| 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