Created
March 9, 2016 02:26
-
-
Save anonymous/cefad88c48e83f5a57fd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 5caf3141ef029b611354f9b50f40d28c4170bec9 Mon Sep 17 00:00:00 2001 | |
From: Josh de Kock <josh@itanimul.li> | |
Date: Fri, 4 Mar 2016 22:48:42 +0000 | |
Subject: [PATCH] [WIP] lavd/jack: Add JACK outdev | |
--- | |
configure | 4 +- | |
libavdevice/Makefile | 3 +- | |
libavdevice/alldevices.c | 2 +- | |
libavdevice/jack.c | 294 +++++------------------------------------------ | |
libavdevice/jack.h | 64 +++++++++++ | |
libavdevice/jack_dec.c | 250 ++++++++++++++++++++++++++++++++++++++++ | |
libavdevice/jack_enc.c | 205 +++++++++++++++++++++++++++++++++ | |
7 files changed, 555 insertions(+), 267 deletions(-) | |
create mode 100644 libavdevice/jack.h | |
create mode 100644 libavdevice/jack_dec.c | |
create mode 100644 libavdevice/jack_enc.c | |
diff --git a/configure b/configure | |
index 81ec105..2517b7e 100755 | |
--- a/configure | |
+++ b/configure | |
@@ -2808,6 +2808,8 @@ gdigrab_indev_select="bmp_decoder" | |
iec61883_indev_deps="libiec61883" | |
jack_indev_deps="jack_jack_h" | |
jack_indev_deps_any="sem_timedwait dispatch_dispatch_h" | |
+jack_outdev_deps="jack_jack_h" | |
+jack_outdev_deps_any="sem_timedwait dispatch_dispatch_h" | |
lavfi_indev_deps="avfilter" | |
libcdio_indev_deps="libcdio" | |
libdc1394_indev_deps="libdc1394" | |
@@ -5741,7 +5743,7 @@ check_header soundcard.h | |
enabled_any alsa_indev alsa_outdev && | |
check_lib2 alsa/asoundlib.h snd_pcm_htimestamp -lasound | |
-enabled jack_indev && check_lib2 jack/jack.h jack_client_open -ljack && | |
+enabled jack_indev jack_outdev && check_lib2 jack/jack.h jack_client_open -ljack && | |
check_func jack_port_get_latency_range -ljack | |
enabled_any sndio_indev sndio_outdev && check_lib2 sndio.h sio_open -lsndio | |
diff --git a/libavdevice/Makefile b/libavdevice/Makefile | |
index 8394e87..329b2c0 100644 | |
--- a/libavdevice/Makefile | |
+++ b/libavdevice/Makefile | |
@@ -27,7 +27,8 @@ OBJS-$(CONFIG_FBDEV_OUTDEV) += fbdev_enc.o \ | |
fbdev_common.o | |
OBJS-$(CONFIG_GDIGRAB_INDEV) += gdigrab.o | |
OBJS-$(CONFIG_IEC61883_INDEV) += iec61883.o | |
-OBJS-$(CONFIG_JACK_INDEV) += jack.o timefilter.o | |
+OBJS-$(CONFIG_JACK_INDEV) += jack_dec.o jack.o timefilter.o | |
+OBJS-$(CONFIG_JACK_OUTDEV) += jack_enc.o jack.o | |
OBJS-$(CONFIG_LAVFI_INDEV) += lavfi.o | |
OBJS-$(CONFIG_OPENAL_INDEV) += openal-dec.o | |
OBJS-$(CONFIG_OPENGL_OUTDEV) += opengl_enc.o | |
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c | |
index 26aecf2..007e891 100644 | |
--- a/libavdevice/alldevices.c | |
+++ b/libavdevice/alldevices.c | |
@@ -56,7 +56,7 @@ void avdevice_register_all(void) | |
REGISTER_INOUTDEV(FBDEV, fbdev); | |
REGISTER_INDEV (GDIGRAB, gdigrab); | |
REGISTER_INDEV (IEC61883, iec61883); | |
- REGISTER_INDEV (JACK, jack); | |
+ REGISTER_INOUTDEV(JACK, jack); | |
REGISTER_INDEV (LAVFI, lavfi); | |
REGISTER_INDEV (OPENAL, openal); | |
REGISTER_OUTDEV (OPENGL, opengl); | |
diff --git a/libavdevice/jack.c b/libavdevice/jack.c | |
index 5455484..e83238f 100644 | |
--- a/libavdevice/jack.c | |
+++ b/libavdevice/jack.c | |
@@ -1,8 +1,6 @@ | |
/* | |
- * JACK Audio Connection Kit input device | |
* Copyright (c) 2009 Samalyse | |
- * Author: Olivier Guilyardi <olivier samalyse com> | |
- * | |
+ * Copyright (c) 2016 Josh de Kock | |
* This file is part of FFmpeg. | |
* | |
* FFmpeg is free software; you can redistribute it and/or | |
@@ -20,7 +18,6 @@ | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
-#include "config.h" | |
#include <semaphore.h> | |
#include <jack/jack.h> | |
@@ -35,134 +32,12 @@ | |
#include "timefilter.h" | |
#include "avdevice.h" | |
-#if HAVE_DISPATCH_DISPATCH_H | |
-#include <dispatch/dispatch.h> | |
-#define sem_t dispatch_semaphore_t | |
-#define sem_init(psem,x,val) *psem = dispatch_semaphore_create(val) | |
-#define sem_post(psem) dispatch_semaphore_signal(*psem) | |
-#define sem_wait(psem) dispatch_semaphore_wait(*psem, DISPATCH_TIME_FOREVER) | |
-#define sem_timedwait(psem, val) dispatch_semaphore_wait(*psem, dispatch_walltime(val, 0)) | |
-#define sem_destroy(psem) dispatch_release(*psem) | |
-#endif | |
- | |
-/** | |
- * Size of the internal FIFO buffers as a number of audio packets | |
- */ | |
-#define FIFO_PACKETS_NUM 16 | |
- | |
-typedef struct JackData { | |
- AVClass *class; | |
- jack_client_t * client; | |
- int activated; | |
- sem_t packet_count; | |
- jack_nframes_t sample_rate; | |
- jack_nframes_t buffer_size; | |
- jack_port_t ** ports; | |
- int nports; | |
- TimeFilter * timefilter; | |
- AVFifoBuffer * new_pkts; | |
- AVFifoBuffer * filled_pkts; | |
- int pkt_xrun; | |
- int jack_xrun; | |
-} JackData; | |
- | |
-static int process_callback(jack_nframes_t nframes, void *arg) | |
-{ | |
- /* Warning: this function runs in realtime. One mustn't allocate memory here | |
- * or do any other thing that could block. */ | |
- | |
- int i, j; | |
- JackData *self = arg; | |
- float * buffer; | |
- jack_nframes_t latency, cycle_delay; | |
- AVPacket pkt; | |
- float *pkt_data; | |
- double cycle_time; | |
- | |
- if (!self->client) | |
- return 0; | |
- | |
- /* The approximate delay since the hardware interrupt as a number of frames */ | |
- cycle_delay = jack_frames_since_cycle_start(self->client); | |
- | |
- /* Retrieve filtered cycle time */ | |
- cycle_time = ff_timefilter_update(self->timefilter, | |
- av_gettime() / 1000000.0 - (double) cycle_delay / self->sample_rate, | |
- self->buffer_size); | |
- | |
- /* Check if an empty packet is available, and if there's enough space to send it back once filled */ | |
- if ((av_fifo_size(self->new_pkts) < sizeof(pkt)) || (av_fifo_space(self->filled_pkts) < sizeof(pkt))) { | |
- self->pkt_xrun = 1; | |
- return 0; | |
- } | |
- | |
- /* Retrieve empty (but allocated) packet */ | |
- av_fifo_generic_read(self->new_pkts, &pkt, sizeof(pkt), NULL); | |
- | |
- pkt_data = (float *) pkt.data; | |
- latency = 0; | |
- | |
- /* Copy and interleave audio data from the JACK buffer into the packet */ | |
- for (i = 0; i < self->nports; i++) { | |
- #if HAVE_JACK_PORT_GET_LATENCY_RANGE | |
- jack_latency_range_t range; | |
- jack_port_get_latency_range(self->ports[i], JackCaptureLatency, &range); | |
- latency += range.max; | |
- #else | |
- latency += jack_port_get_total_latency(self->client, self->ports[i]); | |
- #endif | |
- buffer = jack_port_get_buffer(self->ports[i], self->buffer_size); | |
- for (j = 0; j < self->buffer_size; j++) | |
- pkt_data[j * self->nports + i] = buffer[j]; | |
- } | |
- | |
- /* Timestamp the packet with the cycle start time minus the average latency */ | |
- pkt.pts = (cycle_time - (double) latency / (self->nports * self->sample_rate)) * 1000000.0; | |
- | |
- /* Send the now filled packet back, and increase packet counter */ | |
- av_fifo_generic_write(self->filled_pkts, &pkt, sizeof(pkt), NULL); | |
- sem_post(&self->packet_count); | |
+#include "jack.h" | |
- return 0; | |
-} | |
- | |
-static void shutdown_callback(void *arg) | |
-{ | |
- JackData *self = arg; | |
- self->client = NULL; | |
-} | |
- | |
-static int xrun_callback(void *arg) | |
-{ | |
- JackData *self = arg; | |
- self->jack_xrun = 1; | |
- ff_timefilter_reset(self->timefilter); | |
- return 0; | |
-} | |
- | |
-static int supply_new_packets(JackData *self, AVFormatContext *context) | |
-{ | |
- AVPacket pkt; | |
- int test, pkt_size = self->buffer_size * self->nports * sizeof(float); | |
- | |
- /* Supply the process callback with new empty packets, by filling the new | |
- * packets FIFO buffer with as many packets as possible. process_callback() | |
- * can't do this by itself, because it can't allocate memory in realtime. */ | |
- while (av_fifo_space(self->new_pkts) >= sizeof(pkt)) { | |
- if ((test = av_new_packet(&pkt, pkt_size)) < 0) { | |
- av_log(context, AV_LOG_ERROR, "Could not create packet of size %d\n", pkt_size); | |
- return test; | |
- } | |
- av_fifo_generic_write(self->new_pkts, &pkt, sizeof(pkt), NULL); | |
- } | |
- return 0; | |
-} | |
- | |
-static int start_jack(AVFormatContext *context) | |
+int ff_start_jack(AVFormatContext *context) | |
{ | |
JackData *self = context->priv_data; | |
jack_status_t status; | |
- int i, test; | |
/* Register as a JACK client, using the context filename as client name. */ | |
self->client = jack_client_open(context->filename, JackNullOption, &status); | |
@@ -172,20 +47,21 @@ static int start_jack(AVFormatContext *context) | |
} | |
sem_init(&self->packet_count, 0, 0); | |
- | |
- self->sample_rate = jack_get_sample_rate(self->client); | |
- self->ports = av_malloc_array(self->nports, sizeof(*self->ports)); | |
+ if(!self->sample_rate) | |
+ self->sample_rate = jack_get_sample_rate(self->client); | |
+ self->ports = av_malloc_array(self->nports, sizeof(*self->ports)); | |
if (!self->ports) | |
return AVERROR(ENOMEM); | |
self->buffer_size = jack_get_buffer_size(self->client); | |
/* Register JACK ports */ | |
- for (i = 0; i < self->nports; i++) { | |
+ for (int i = 0; i < self->nports; i++) { | |
char str[16]; | |
- snprintf(str, sizeof(str), "input_%d", i + 1); | |
+ snprintf(str, sizeof(str), | |
+ (self->port_type == JackPortIsInput) ? "input_%d" : "output_%d", i + 1); | |
self->ports[i] = jack_port_register(self->client, str, | |
JACK_DEFAULT_AUDIO_TYPE, | |
- JackPortIsInput, 0); | |
+ self->port_type, 0); | |
if (!self->ports[i]) { | |
av_log(context, AV_LOG_ERROR, "Unable to register port %s:%s\n", | |
context->filename, str); | |
@@ -195,9 +71,8 @@ static int start_jack(AVFormatContext *context) | |
} | |
/* Register JACK callbacks */ | |
- jack_set_process_callback(self->client, process_callback, self); | |
- jack_on_shutdown(self->client, shutdown_callback, self); | |
- jack_set_xrun_callback(self->client, xrun_callback, self); | |
+ jack_on_shutdown(self->client, ff_shutdown_callback, self); | |
+ jack_set_xrun_callback(self->client, ff_xrun_callback, self); | |
/* Create time filter */ | |
self->timefilter = ff_timefilter_new (1.0 / self->sample_rate, self->buffer_size, 1.5); | |
@@ -214,26 +89,26 @@ static int start_jack(AVFormatContext *context) | |
jack_client_close(self->client); | |
return AVERROR(ENOMEM); | |
} | |
- if ((test = supply_new_packets(self, context))) { | |
- jack_client_close(self->client); | |
- return test; | |
- } | |
return 0; | |
} | |
-static void free_pkt_fifo(AVFifoBuffer **fifo) | |
+void ff_shutdown_callback(void *arg) | |
{ | |
- AVPacket pkt; | |
- while (av_fifo_size(*fifo)) { | |
- av_fifo_generic_read(*fifo, &pkt, sizeof(pkt), NULL); | |
- av_packet_unref(&pkt); | |
- } | |
- av_fifo_freep(fifo); | |
+ JackData *self = arg; | |
+ self->client = NULL; | |
} | |
-static void stop_jack(JackData *self) | |
+int ff_xrun_callback(void *arg) | |
+{ | |
+ JackData *self = arg; | |
+ self->jack_xrun = 1; | |
+ ff_timefilter_reset(self->timefilter); | |
+ return 0; | |
+} | |
+ | |
+void ff_stop_jack(JackData *self) | |
{ | |
if (self->client) { | |
if (self->activated) | |
@@ -247,122 +122,13 @@ static void stop_jack(JackData *self) | |
ff_timefilter_destroy(self->timefilter); | |
} | |
-static int audio_read_header(AVFormatContext *context) | |
+void free_pkt_fifo(AVFifoBuffer **fifo) | |
{ | |
- JackData *self = context->priv_data; | |
- AVStream *stream; | |
- int test; | |
- | |
- if ((test = start_jack(context))) | |
- return test; | |
- | |
- stream = avformat_new_stream(context, NULL); | |
- if (!stream) { | |
- stop_jack(self); | |
- return AVERROR(ENOMEM); | |
- } | |
- | |
- stream->codec->codec_type = AVMEDIA_TYPE_AUDIO; | |
-#if HAVE_BIGENDIAN | |
- stream->codec->codec_id = AV_CODEC_ID_PCM_F32BE; | |
-#else | |
- stream->codec->codec_id = AV_CODEC_ID_PCM_F32LE; | |
-#endif | |
- stream->codec->sample_rate = self->sample_rate; | |
- stream->codec->channels = self->nports; | |
- | |
- avpriv_set_pts_info(stream, 64, 1, 1000000); /* 64 bits pts in us */ | |
- return 0; | |
-} | |
- | |
-static int audio_read_packet(AVFormatContext *context, AVPacket *pkt) | |
-{ | |
- JackData *self = context->priv_data; | |
- struct timespec timeout = {0, 0}; | |
- int test; | |
- | |
- /* Activate the JACK client on first packet read. Activating the JACK client | |
- * means that process_callback() starts to get called at regular interval. | |
- * If we activate it in audio_read_header(), we're actually reading audio data | |
- * from the device before instructed to, and that may result in an overrun. */ | |
- if (!self->activated) { | |
- if (!jack_activate(self->client)) { | |
- self->activated = 1; | |
- av_log(context, AV_LOG_INFO, | |
- "JACK client registered and activated (rate=%dHz, buffer_size=%d frames)\n", | |
- self->sample_rate, self->buffer_size); | |
- } else { | |
- av_log(context, AV_LOG_ERROR, "Unable to activate JACK client\n"); | |
- return AVERROR(EIO); | |
- } | |
- } | |
- | |
- /* Wait for a packet coming back from process_callback(), if one isn't available yet */ | |
- timeout.tv_sec = av_gettime() / 1000000 + 2; | |
- if (sem_timedwait(&self->packet_count, &timeout)) { | |
- if (errno == ETIMEDOUT) { | |
- av_log(context, AV_LOG_ERROR, | |
- "Input error: timed out when waiting for JACK process callback output\n"); | |
- } else { | |
- char errbuf[128]; | |
- int ret = AVERROR(errno); | |
- av_strerror(ret, errbuf, sizeof(errbuf)); | |
- av_log(context, AV_LOG_ERROR, "Error while waiting for audio packet: %s\n", | |
- errbuf); | |
- } | |
- if (!self->client) | |
- av_log(context, AV_LOG_ERROR, "Input error: JACK server is gone\n"); | |
- | |
- return AVERROR(EIO); | |
- } | |
- | |
- if (self->pkt_xrun) { | |
- av_log(context, AV_LOG_WARNING, "Audio packet xrun\n"); | |
- self->pkt_xrun = 0; | |
- } | |
- | |
- if (self->jack_xrun) { | |
- av_log(context, AV_LOG_WARNING, "JACK xrun\n"); | |
- self->jack_xrun = 0; | |
+ AVPacket pkt; | |
+ while (av_fifo_size(*fifo)) { | |
+ av_fifo_generic_read(*fifo, &pkt, sizeof(pkt), NULL); | |
+ av_packet_unref(&pkt); | |
} | |
- | |
- /* Retrieve the packet filled with audio data by process_callback() */ | |
- av_fifo_generic_read(self->filled_pkts, pkt, sizeof(*pkt), NULL); | |
- | |
- if ((test = supply_new_packets(self, context))) | |
- return test; | |
- | |
- return 0; | |
-} | |
- | |
-static int audio_read_close(AVFormatContext *context) | |
-{ | |
- JackData *self = context->priv_data; | |
- stop_jack(self); | |
- return 0; | |
+ av_fifo_freep(fifo); | |
} | |
-#define OFFSET(x) offsetof(JackData, x) | |
-static const AVOption options[] = { | |
- { "channels", "Number of audio channels.", OFFSET(nports), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, | |
- { NULL }, | |
-}; | |
- | |
-static const AVClass jack_indev_class = { | |
- .class_name = "JACK indev", | |
- .item_name = av_default_item_name, | |
- .option = options, | |
- .version = LIBAVUTIL_VERSION_INT, | |
- .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT, | |
-}; | |
- | |
-AVInputFormat ff_jack_demuxer = { | |
- .name = "jack", | |
- .long_name = NULL_IF_CONFIG_SMALL("JACK Audio Connection Kit"), | |
- .priv_data_size = sizeof(JackData), | |
- .read_header = audio_read_header, | |
- .read_packet = audio_read_packet, | |
- .read_close = audio_read_close, | |
- .flags = AVFMT_NOFILE, | |
- .priv_class = &jack_indev_class, | |
-}; | |
diff --git a/libavdevice/jack.h b/libavdevice/jack.h | |
new file mode 100644 | |
index 0000000..7642f3d | |
--- /dev/null | |
+++ b/libavdevice/jack.h | |
@@ -0,0 +1,64 @@ | |
+/* | |
+ * Copyright (c) 2009 Samalyse | |
+ * Copyright (c) 2016 Josh de Kock | |
+ * | |
+ * This file is part of FFmpeg. | |
+ * | |
+ * FFmpeg is free software; you can redistribute it and/or | |
+ * modify it under the terms of the GNU Lesser General Public | |
+ * License as published by the Free Software Foundation; either | |
+ * version 2.1 of the License, or (at your option) any later version. | |
+ * | |
+ * FFmpeg is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * Lesser General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU Lesser General Public | |
+ * License along with FFmpeg; if not, write to the Free Software | |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
+ */ | |
+ | |
+#ifndef AVDEVICE_JACK_H | |
+ | |
+#include <jack/ringbuffer.h> | |
+ | |
+#if HAVE_DISPATCH_DISPATCH_H | |
+#include <dispatch/dispatch.h> | |
+#define sem_t dispatch_semaphore_t | |
+#define sem_init(psem,x,val) *psem = dispatch_semaphore_create(val) | |
+#define sem_post(psem) dispatch_semaphore_signal(*psem) | |
+#define sem_wait(psem) dispatch_semaphore_wait(*psem, DISPATCH_TIME_FOREVER) | |
+#define sem_timedwait(psem, val) dispatch_semaphore_wait(*psem, dispatch_walltime(val, 0)) | |
+#define sem_destroy(psem) dispatch_release(*psem) | |
+#endif | |
+ | |
+/** | |
+ * Size of the internal FIFO buffers as a number of audio packets | |
+ */ | |
+#define FIFO_PACKETS_NUM 16 | |
+ | |
+typedef struct JackData { | |
+ AVClass *class; | |
+ jack_client_t * client; | |
+ int activated; | |
+ sem_t packet_count; | |
+ jack_nframes_t sample_rate; | |
+ jack_nframes_t buffer_size; | |
+ jack_port_t ** ports; | |
+ int nports; | |
+ int port_type; ///< JACK port type, either JackPortIsInput or JackPortIsOutput | |
+ TimeFilter * timefilter; | |
+ AVFifoBuffer * new_pkts; | |
+ AVFifoBuffer * filled_pkts; | |
+ int pkt_xrun; | |
+ int jack_xrun; | |
+} JackData; | |
+ | |
+int ff_start_jack(AVFormatContext *context); | |
+void ff_stop_jack(JackData *self); | |
+void ff_shutdown_callback(void *arg); | |
+void free_pkt_fifo(AVFifoBuffer **fifo); | |
+int ff_xrun_callback(void *arg); | |
+ | |
+#endif | |
diff --git a/libavdevice/jack_dec.c b/libavdevice/jack_dec.c | |
new file mode 100644 | |
index 0000000..0b35513 | |
--- /dev/null | |
+++ b/libavdevice/jack_dec.c | |
@@ -0,0 +1,250 @@ | |
+/* | |
+ * Copyright (c) 2009 Samalyse | |
+ * Copyright (c) 2016 Josh de Kock | |
+ * | |
+ * This file is part of FFmpeg. | |
+ * | |
+ * FFmpeg is free software; you can redistribute it and/or | |
+ * modify it under the terms of the GNU Lesser General Public | |
+ * License as published by the Free Software Foundation; either | |
+ * version 2.1 of the License, or (at your option) any later version. | |
+ * | |
+ * FFmpeg is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * Lesser General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU Lesser General Public | |
+ * License along with FFmpeg; if not, write to the Free Software | |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
+ */ | |
+ | |
+/** | |
+ * @file | |
+ * JACK Audio Connection Kit input device | |
+ * | |
+ */ | |
+ | |
+#include "config.h" | |
+#include <semaphore.h> | |
+#include <jack/jack.h> | |
+ | |
+#include "libavutil/internal.h" | |
+#include "libavutil/log.h" | |
+#include "libavutil/fifo.h" | |
+#include "libavutil/opt.h" | |
+#include "libavutil/time.h" | |
+#include "libavcodec/avcodec.h" | |
+#include "libavformat/avformat.h" | |
+#include "libavformat/internal.h" | |
+#include "timefilter.h" | |
+#include "avdevice.h" | |
+ | |
+#include "jack.h" | |
+ | |
+static int process_callback(jack_nframes_t nframes, void *arg) | |
+{ | |
+ /* Warning: this function runs in realtime. One mustn't allocate memory here | |
+ * or do any other thing that could block. */ | |
+ | |
+ int i, j; | |
+ JackData *self = arg; | |
+ float * buffer; | |
+ jack_nframes_t latency, cycle_delay; | |
+ AVPacket pkt; | |
+ float *pkt_data; | |
+ double cycle_time; | |
+ | |
+ if (!self->client) | |
+ return 0; | |
+ | |
+ /* The approximate delay since the hardware interrupt as a number of frames */ | |
+ cycle_delay = jack_frames_since_cycle_start(self->client); | |
+ | |
+ /* Retrieve filtered cycle time */ | |
+ cycle_time = ff_timefilter_update(self->timefilter, | |
+ av_gettime() / 1000000.0 - (double) cycle_delay / self->sample_rate, | |
+ self->buffer_size); | |
+ | |
+ /* Check if an empty packet is available, and if there's enough space to send it back once filled */ | |
+ if ((av_fifo_size(self->new_pkts) < sizeof(pkt)) || (av_fifo_space(self->filled_pkts) < sizeof(pkt))) { | |
+ self->pkt_xrun = 1; | |
+ return 0; | |
+ } | |
+ | |
+ /* Retrieve empty (but allocated) packet */ | |
+ av_fifo_generic_read(self->new_pkts, &pkt, sizeof(pkt), NULL); | |
+ | |
+ pkt_data = (float *) pkt.data; | |
+ latency = 0; | |
+ | |
+ /* Copy and interleave audio data from the JACK buffer into the packet */ | |
+ for (i = 0; i < self->nports; i++) { | |
+ #if HAVE_JACK_PORT_GET_LATENCY_RANGE | |
+ jack_latency_range_t range; | |
+ jack_port_get_latency_range(self->ports[i], JackCaptureLatency, &range); | |
+ latency += range.max; | |
+ #else | |
+ latency += jack_port_get_total_latency(self->client, self->ports[i]); | |
+ #endif | |
+ buffer = jack_port_get_buffer(self->ports[i], self->buffer_size); | |
+ for (j = 0; j < self->buffer_size; j++) | |
+ pkt_data[j * self->nports + i] = buffer[j]; | |
+ } | |
+ | |
+ /* Timestamp the packet with the cycle start time minus the average latency */ | |
+ pkt.pts = (cycle_time - (double) latency / (self->nports * self->sample_rate)) * 1000000.0; | |
+ | |
+ /* Send the now filled packet back, and increase packet counter */ | |
+ av_fifo_generic_write(self->filled_pkts, &pkt, sizeof(pkt), NULL); | |
+ sem_post(&self->packet_count); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int supply_new_packets(JackData *self, AVFormatContext *context) | |
+{ | |
+ AVPacket pkt; | |
+ int test, pkt_size = self->buffer_size * self->nports * sizeof(float); | |
+ | |
+ /* Supply the process callback with new empty packets, by filling the new | |
+ * packets FIFO buffer with as many packets as possible. process_callback() | |
+ * can't do this by itself, because it can't allocate memory in realtime. */ | |
+ while (av_fifo_space(self->new_pkts) >= sizeof(pkt)) { | |
+ if ((test = av_new_packet(&pkt, pkt_size)) < 0) { | |
+ av_log(context, AV_LOG_ERROR, "Could not create packet of size %d\n", pkt_size); | |
+ return test; | |
+ } | |
+ av_fifo_generic_write(self->new_pkts, &pkt, sizeof(pkt), NULL); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int audio_read_header(AVFormatContext *context) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ AVStream *stream; | |
+ int test; | |
+ self->port_type = JackPortIsInput; | |
+ | |
+ if ((test = ff_start_jack(context))) | |
+ return test; | |
+ | |
+ jack_set_process_callback(self->client, process_callback, self); | |
+ | |
+ if ((test = supply_new_packets(self, context))) { | |
+ jack_client_close(self->client); | |
+ return test; | |
+ } | |
+ | |
+ stream = avformat_new_stream(context, NULL); | |
+ if (!stream) { | |
+ ff_stop_jack(self); | |
+ return AVERROR(ENOMEM); | |
+ } | |
+ | |
+ stream->codec->codec_type = AVMEDIA_TYPE_AUDIO; | |
+#if HAVE_BIGENDIAN | |
+ stream->codec->codec_id = AV_CODEC_ID_PCM_F32BE; | |
+#else | |
+ stream->codec->codec_id = AV_CODEC_ID_PCM_F32LE; | |
+#endif | |
+ stream->codec->sample_rate = self->sample_rate; | |
+ stream->codec->channels = self->nports; | |
+ | |
+ avpriv_set_pts_info(stream, 64, 1, 1000000); /* 64 bits pts in us */ | |
+ return 0; | |
+} | |
+ | |
+static int audio_read_packet(AVFormatContext *context, AVPacket *pkt) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ struct timespec timeout = {0, 0}; | |
+ int test; | |
+ | |
+ /* Activate the JACK client on first packet read. Activating the JACK client | |
+ * means that process_callback() starts to get called at regular interval. | |
+ * If we activate it in audio_read_header(), we're actually reading audio data | |
+ * from the device before instructed to, and that may result in an overrun. */ | |
+ if (!self->activated) { | |
+ if (!jack_activate(self->client)) { | |
+ self->activated = 1; | |
+ av_log(context, AV_LOG_INFO, | |
+ "JACK client registered and activated (rate=%dHz, buffer_size=%d frames)\n", | |
+ self->sample_rate, self->buffer_size); | |
+ } else { | |
+ av_log(context, AV_LOG_ERROR, "Unable to activate JACK client\n"); | |
+ return AVERROR(EIO); | |
+ } | |
+ } | |
+ | |
+ /* Wait for a packet coming back from process_callback(), if one isn't available yet */ | |
+ timeout.tv_sec = av_gettime() / 1000000 + 2; | |
+ if (sem_timedwait(&self->packet_count, &timeout)) { | |
+ if (errno == ETIMEDOUT) { | |
+ av_log(context, AV_LOG_ERROR, | |
+ "Input error: timed out when waiting for JACK process callback output\n"); | |
+ } else { | |
+ char errbuf[128]; | |
+ int ret = AVERROR(errno); | |
+ av_strerror(ret, errbuf, sizeof(errbuf)); | |
+ av_log(context, AV_LOG_ERROR, "Error while waiting for audio packet: %s\n", | |
+ errbuf); | |
+ } | |
+ if (!self->client) | |
+ av_log(context, AV_LOG_ERROR, "Input error: JACK server is gone\n"); | |
+ | |
+ return AVERROR(EIO); | |
+ } | |
+ | |
+ if (self->pkt_xrun) { | |
+ av_log(context, AV_LOG_WARNING, "Audio packet xrun\n"); | |
+ self->pkt_xrun = 0; | |
+ } | |
+ | |
+ if (self->jack_xrun) { | |
+ av_log(context, AV_LOG_WARNING, "JACK xrun\n"); | |
+ self->jack_xrun = 0; | |
+ } | |
+ | |
+ /* Retrieve the packet filled with audio data by process_callback() */ | |
+ av_fifo_generic_read(self->filled_pkts, pkt, sizeof(*pkt), NULL); | |
+ | |
+ if ((test = supply_new_packets(self, context))) | |
+ return test; | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int audio_read_close(AVFormatContext *context) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ ff_stop_jack(self); | |
+ return 0; | |
+} | |
+ | |
+#define OFFSET(x) offsetof(JackData, x) | |
+static const AVOption options[] = { | |
+ { "channels", "Number of audio channels.", OFFSET(nports), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, | |
+ { NULL }, | |
+}; | |
+ | |
+static const AVClass jack_indev_class = { | |
+ .class_name = "JACK indev", | |
+ .item_name = av_default_item_name, | |
+ .option = options, | |
+ .version = LIBAVUTIL_VERSION_INT, | |
+ .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT, | |
+}; | |
+ | |
+AVInputFormat ff_jack_demuxer = { | |
+ .name = "jack", | |
+ .long_name = NULL_IF_CONFIG_SMALL("JACK Audio Connection Kit"), | |
+ .priv_data_size = sizeof(JackData), | |
+ .read_header = audio_read_header, | |
+ .read_packet = audio_read_packet, | |
+ .read_close = audio_read_close, | |
+ .flags = AVFMT_NOFILE, | |
+ .priv_class = &jack_indev_class, | |
+}; | |
+ | |
diff --git a/libavdevice/jack_enc.c b/libavdevice/jack_enc.c | |
new file mode 100644 | |
index 0000000..195cf23 | |
--- /dev/null | |
+++ b/libavdevice/jack_enc.c | |
@@ -0,0 +1,205 @@ | |
+/* | |
+ * Copyright (c) 2009 Samalyse | |
+ * Copyright (c) 2016 Josh de Kock | |
+ * | |
+ * This file is part of FFmpeg. | |
+ * | |
+ * FFmpeg is free software; you can redistribute it and/or | |
+ * modify it under the terms of the GNU Lesser General Public | |
+ * License as published by the Free Software Foundation; either | |
+ * version 2.1 of the License, or (at your option) any later version. | |
+ * | |
+ * FFmpeg is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * Lesser General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU Lesser General Public | |
+ * License along with FFmpeg; if not, write to the Free Software | |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
+ */ | |
+ | |
+/** | |
+ * @file | |
+ * JACK Audio Connection Kit output device | |
+ * | |
+ */ | |
+ | |
+#include "config.h" | |
+#include <semaphore.h> | |
+#include <jack/jack.h> | |
+ | |
+#include "libavutil/internal.h" | |
+#include "libavutil/log.h" | |
+#include "libavutil/fifo.h" | |
+#include "libavutil/opt.h" | |
+#include "libavutil/time.h" | |
+#include "libavcodec/avcodec.h" | |
+#include "libavformat/avformat.h" | |
+#include "libavformat/internal.h" | |
+#include "timefilter.h" | |
+#include "avdevice.h" | |
+ | |
+#include "jack.h" | |
+ | |
+static int process_callback(jack_nframes_t nframes, void *arg) | |
+{ | |
+ JackData *self = (JackData*)arg; | |
+ AVPacket *pkt = NULL; | |
+ | |
+ jack_nframes_t cycle_delay; | |
+ double cycle_time; | |
+ | |
+ /* The approximate delay since the hardware interrupt as a number of frames */ | |
+ cycle_delay = jack_frames_since_cycle_start(self->client); | |
+ | |
+ /* Retrieve filtered cycle time */ | |
+ cycle_time = ff_timefilter_update(self->timefilter, | |
+ av_gettime() / 1000000.0 - (double) cycle_delay / self->sample_rate, | |
+ self->buffer_size); | |
+ | |
+ if ((av_fifo_size(self->filled_pkts) < sizeof(pkt))){ | |
+ self->pkt_xrun = 1; | |
+ return 0; | |
+ } | |
+ av_fifo_generic_read(self->filled_pkts, &pkt, sizeof(pkt), NULL); | |
+ | |
+ float *pkt_data = (float *)(pkt->data ? pkt->data : pkt->buf->data); | |
+ float **data = (float **)av_malloc(self->nports*sizeof(float*)); | |
+ int size = (nframes < self->buffer_size) ? nframes : self->buffer_size; | |
+ for (int i = 0; i < self->nports; ++i) | |
+ data[i] = (float*)jack_port_get_buffer(self->ports[i], size); | |
+ | |
+ for (int i = 0, j = 0; i < size; i++) { | |
+ data[j][i] = pkt_data[i]; | |
+ j = (j+1) % self->nports; | |
+ } | |
+ av_free(data); | |
+ sem_post(&self->packet_count); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int audio_write_header(AVFormatContext *context) | |
+{ | |
+ av_log(context, AV_LOG_INFO, "JACK audio_write_header called"); | |
+ JackData *self = context->priv_data; | |
+ if (context->nb_streams != 1 || context->streams[0]->codec->codec_type != AVMEDIA_TYPE_AUDIO) { | |
+ av_log(context, AV_LOG_ERROR, "Only a single audio stream is supported.\n"); | |
+ return AVERROR(EINVAL); | |
+ } | |
+ AVStream *stream = context->streams[0]; | |
+ self->port_type = JackPortIsOutput; | |
+ | |
+ self->sample_rate = stream->codec->sample_rate; | |
+ self->nports = stream->codec->channels; | |
+ int test; | |
+ if ((test = ff_start_jack(context))) | |
+ return test; | |
+ jack_set_process_callback(self->client, process_callback, self); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int audio_write_packet(AVFormatContext *context, AVPacket *pkt) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ | |
+ if (!self->activated) { | |
+ if (!jack_activate(self->client)) { | |
+ self->activated = 1; | |
+ av_log(context, AV_LOG_INFO, | |
+ "JACK client registered and activated (rate=%dHz, buffer_size=%d frames)\n", | |
+ self->sample_rate, self->buffer_size); | |
+ } else { | |
+ av_log(context, AV_LOG_ERROR, "Unable to activate JACK client\n"); | |
+ return AVERROR(EIO); | |
+ } | |
+ } | |
+ if (av_fifo_space(self->filled_pkts) >= sizeof(pkt)) { | |
+ av_fifo_generic_write(self->filled_pkts, &pkt, sizeof(pkt), NULL); | |
+ } | |
+ struct timespec timeout = {0, 0}; | |
+ timeout.tv_sec = av_gettime() / 1000000 + 2; | |
+ | |
+ /* Wait for packet to be sent to JACK in process_callback() */ | |
+ if (sem_timedwait(&self->packet_count, &timeout)) { | |
+ if (errno == ETIMEDOUT) { | |
+ av_log(context, AV_LOG_ERROR, | |
+ "Input error: timed out write_header waiting for JACK process callback output\n"); | |
+ } else { | |
+ char errbuf[128]; | |
+ int ret = AVERROR(errno); | |
+ av_strerror(ret, errbuf, sizeof(errbuf)); | |
+ av_log(context, AV_LOG_ERROR, "Error while waiting for audio packet to write: %s\n", | |
+ errbuf); | |
+ } | |
+ if (!self->client) | |
+ av_log(context, AV_LOG_ERROR, "Input error: JACK server is gone\n"); | |
+ | |
+ return AVERROR(EIO); | |
+ } | |
+ | |
+ if (self->pkt_xrun) { | |
+ av_log(context, AV_LOG_WARNING, "Audio packet xrun\n"); | |
+ self->pkt_xrun = 0; | |
+ } | |
+ | |
+ if (self->jack_xrun) { | |
+ av_log(context, AV_LOG_WARNING, "JACK xrun\n"); | |
+ self->jack_xrun = 0; | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int audio_write_close(AVFormatContext *context) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ ff_stop_jack(self); | |
+ return 0; | |
+} | |
+ | |
+static int audio_write_frame(AVFormatContext *context, int stream_index, | |
+ AVFrame **frame, unsigned flags) | |
+{ | |
+ JackData *self = context->priv_data; | |
+ AVPacket pkt; | |
+ | |
+ /* ff_alsa_open() should have accepted only supported formats */ | |
+ if ((flags & AV_WRITE_UNCODED_FRAME_QUERY)) | |
+ return av_sample_fmt_is_planar(context->streams[stream_index]->codec->sample_fmt) ? | |
+ AVERROR(EINVAL) : 0; | |
+ /* set only used fields */ | |
+ pkt.data = (*frame)->data[0]; | |
+ pkt.size = (*frame)->nb_samples * self->buffer_size; | |
+ pkt.dts = (*frame)->pkt_dts; | |
+ pkt.duration = av_frame_get_pkt_duration(*frame); | |
+ return audio_write_packet(context, &pkt); | |
+} | |
+ | |
+#define OFFSET(x) offsetof(JackData, x) | |
+ | |
+#define DEFAULT_CODEC_ID AV_NE(AV_CODEC_ID_PCM_F32BE, AV_CODEC_ID_PCM_F32LE) | |
+ | |
+static const AVClass jack_outdev_class = { | |
+ .class_name = "JACK outdev", | |
+ .item_name = av_default_item_name, | |
+ .version = LIBAVUTIL_VERSION_INT, | |
+ .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT, | |
+}; | |
+ | |
+AVOutputFormat ff_jack_muxer = { | |
+ .name = "jack", | |
+ .long_name = NULL_IF_CONFIG_SMALL("JACK Audio Connection Kit"), | |
+ .priv_data_size = sizeof(JackData), | |
+ .video_codec = AV_CODEC_ID_NONE, | |
+ .audio_codec = DEFAULT_CODEC_ID, | |
+ .write_header = audio_write_header, | |
+ .write_packet = audio_write_packet, | |
+ .write_uncoded_frame = audio_write_frame, | |
+ .write_trailer = audio_write_close, | |
+ .flags = AVFMT_NOFILE, | |
+ .priv_class = &jack_outdev_class, | |
+}; | |
+ | |
-- | |
2.5.4 (Apple Git-61) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment