2009-04-03 02:53:47 +03:00
|
|
|
/*
|
|
|
|
* JACK Audio Connection Kit input device
|
|
|
|
* Copyright (c) 2009 Samalyse
|
|
|
|
* Author: Olivier Guilyardi <olivier samalyse com>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include <semaphore.h>
|
|
|
|
#include <jack/jack.h>
|
|
|
|
|
2015-04-08 17:27:42 +02:00
|
|
|
#include "libavutil/internal.h"
|
2009-04-03 02:53:47 +03:00
|
|
|
#include "libavutil/log.h"
|
|
|
|
#include "libavutil/fifo.h"
|
2011-07-17 09:08:57 +03:00
|
|
|
#include "libavutil/opt.h"
|
2012-07-27 17:28:36 +03:00
|
|
|
#include "libavutil/time.h"
|
2009-04-03 02:53:47 +03:00
|
|
|
#include "libavcodec/avcodec.h"
|
|
|
|
#include "libavformat/avformat.h"
|
2011-11-29 21:28:15 +03:00
|
|
|
#include "libavformat/internal.h"
|
2011-10-21 12:47:39 +03:00
|
|
|
#include "timefilter.h"
|
2011-05-26 20:11:25 +03:00
|
|
|
#include "avdevice.h"
|
2009-04-03 02:53:47 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Size of the internal FIFO buffers as a number of audio packets
|
|
|
|
*/
|
|
|
|
#define FIFO_PACKETS_NUM 16
|
|
|
|
|
2014-07-17 18:51:27 +03:00
|
|
|
typedef struct JackData {
|
2011-07-17 09:08:57 +03:00
|
|
|
AVClass *class;
|
2009-04-03 02:53:47 +03:00
|
|
|
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++) {
|
2012-06-12 01:22:31 +03:00
|
|
|
#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
|
2009-04-03 02:53:47 +03:00
|
|
|
latency += jack_port_get_total_latency(self->client, self->ports[i]);
|
2012-06-12 01:22:31 +03:00
|
|
|
#endif
|
2009-04-03 02:53:47 +03:00
|
|
|
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 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;
|
|
|
|
}
|
|
|
|
|
2011-07-17 09:08:57 +03:00
|
|
|
static int start_jack(AVFormatContext *context)
|
2009-04-03 02:53:47 +03:00
|
|
|
{
|
|
|
|
JackData *self = context->priv_data;
|
|
|
|
jack_status_t status;
|
|
|
|
int i, test;
|
|
|
|
|
|
|
|
/* Register as a JACK client, using the context filename as client name. */
|
2009-04-15 01:21:53 +03:00
|
|
|
self->client = jack_client_open(context->filename, JackNullOption, &status);
|
2009-04-03 02:53:47 +03:00
|
|
|
if (!self->client) {
|
|
|
|
av_log(context, AV_LOG_ERROR, "Unable to register as a JACK client\n");
|
|
|
|
return AVERROR(EIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
sem_init(&self->packet_count, 0, 0);
|
|
|
|
|
|
|
|
self->sample_rate = jack_get_sample_rate(self->client);
|
2014-04-23 22:13:31 +03:00
|
|
|
self->ports = av_malloc_array(self->nports, sizeof(*self->ports));
|
2015-04-19 13:28:58 +02:00
|
|
|
if (!self->ports)
|
|
|
|
return AVERROR(ENOMEM);
|
2009-04-03 02:53:47 +03:00
|
|
|
self->buffer_size = jack_get_buffer_size(self->client);
|
|
|
|
|
|
|
|
/* Register JACK ports */
|
|
|
|
for (i = 0; i < self->nports; i++) {
|
|
|
|
char str[16];
|
|
|
|
snprintf(str, sizeof(str), "input_%d", i + 1);
|
|
|
|
self->ports[i] = jack_port_register(self->client, str,
|
|
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
|
|
JackPortIsInput, 0);
|
|
|
|
if (!self->ports[i]) {
|
|
|
|
av_log(context, AV_LOG_ERROR, "Unable to register port %s:%s\n",
|
|
|
|
context->filename, str);
|
|
|
|
jack_client_close(self->client);
|
|
|
|
return AVERROR(EIO);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
|
|
/* Create time filter */
|
2012-02-15 21:15:45 +03:00
|
|
|
self->timefilter = ff_timefilter_new (1.0 / self->sample_rate, self->buffer_size, 1.5);
|
2013-10-22 20:46:37 +03:00
|
|
|
if (!self->timefilter) {
|
|
|
|
jack_client_close(self->client);
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
2009-04-03 02:53:47 +03:00
|
|
|
|
|
|
|
/* Create FIFO buffers */
|
2014-05-11 06:13:31 +03:00
|
|
|
self->filled_pkts = av_fifo_alloc_array(FIFO_PACKETS_NUM, sizeof(AVPacket));
|
2009-04-03 02:53:47 +03:00
|
|
|
/* New packets FIFO with one extra packet for safety against underruns */
|
2014-05-11 06:13:31 +03:00
|
|
|
self->new_pkts = av_fifo_alloc_array((FIFO_PACKETS_NUM + 1), sizeof(AVPacket));
|
2015-06-03 15:16:48 +02:00
|
|
|
if (!self->new_pkts) {
|
|
|
|
jack_client_close(self->client);
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
2009-04-03 02:53:47 +03:00
|
|
|
if ((test = supply_new_packets(self, context))) {
|
|
|
|
jack_client_close(self->client);
|
|
|
|
return test;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-05-06 22:38:34 +03:00
|
|
|
static void free_pkt_fifo(AVFifoBuffer **fifo)
|
2009-04-03 02:53:47 +03:00
|
|
|
{
|
|
|
|
AVPacket pkt;
|
2014-05-06 22:38:34 +03:00
|
|
|
while (av_fifo_size(*fifo)) {
|
|
|
|
av_fifo_generic_read(*fifo, &pkt, sizeof(pkt), NULL);
|
2015-10-23 11:11:31 +02:00
|
|
|
av_packet_unref(&pkt);
|
2009-04-03 02:53:47 +03:00
|
|
|
}
|
2014-05-06 22:38:34 +03:00
|
|
|
av_fifo_freep(fifo);
|
2009-04-03 02:53:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void stop_jack(JackData *self)
|
|
|
|
{
|
|
|
|
if (self->client) {
|
|
|
|
if (self->activated)
|
|
|
|
jack_deactivate(self->client);
|
|
|
|
jack_client_close(self->client);
|
|
|
|
}
|
|
|
|
sem_destroy(&self->packet_count);
|
2014-05-06 22:38:34 +03:00
|
|
|
free_pkt_fifo(&self->new_pkts);
|
|
|
|
free_pkt_fifo(&self->filled_pkts);
|
2009-04-03 02:53:47 +03:00
|
|
|
av_freep(&self->ports);
|
|
|
|
ff_timefilter_destroy(self->timefilter);
|
|
|
|
}
|
|
|
|
|
2012-01-12 15:20:36 +03:00
|
|
|
static int audio_read_header(AVFormatContext *context)
|
2009-04-03 02:53:47 +03:00
|
|
|
{
|
|
|
|
JackData *self = context->priv_data;
|
|
|
|
AVStream *stream;
|
|
|
|
int test;
|
|
|
|
|
2011-07-17 09:08:57 +03:00
|
|
|
if ((test = start_jack(context)))
|
2009-04-03 02:53:47 +03:00
|
|
|
return test;
|
|
|
|
|
2011-06-18 12:43:24 +03:00
|
|
|
stream = avformat_new_stream(context, NULL);
|
2009-04-03 02:53:47 +03:00
|
|
|
if (!stream) {
|
|
|
|
stop_jack(self);
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
lavf: replace AVStream.codec with AVStream.codecpar
Currently, AVStream contains an embedded AVCodecContext instance, which
is used by demuxers to export stream parameters to the caller and by
muxers to receive stream parameters from the caller. It is also used
internally as the codec context that is passed to parsers.
In addition, it is also widely used by the callers as the decoding (when
demuxer) or encoding (when muxing) context, though this has been
officially discouraged since Libav 11.
There are multiple important problems with this approach:
- the fields in AVCodecContext are in general one of
* stream parameters
* codec options
* codec state
However, it's not clear which ones are which. It is consequently
unclear which fields are a demuxer allowed to set or a muxer allowed to
read. This leads to erratic behaviour depending on whether decoding or
encoding is being performed or not (and whether it uses the AVStream
embedded codec context).
- various synchronization issues arising from the fact that the same
context is used by several different APIs (muxers/demuxers,
parsers, bitstream filters and encoders/decoders) simultaneously, with
there being no clear rules for who can modify what and the different
processes being typically delayed with respect to each other.
- avformat_find_stream_info() making it necessary to support opening
and closing a single codec context multiple times, thus
complicating the semantics of freeing various allocated objects in the
codec context.
Those problems are resolved by replacing the AVStream embedded codec
context with a newly added AVCodecParameters instance, which stores only
the stream parameters exported by the demuxers or read by the muxers.
2014-06-18 21:42:52 +03:00
|
|
|
stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
|
2009-07-26 15:20:04 +03:00
|
|
|
#if HAVE_BIGENDIAN
|
lavf: replace AVStream.codec with AVStream.codecpar
Currently, AVStream contains an embedded AVCodecContext instance, which
is used by demuxers to export stream parameters to the caller and by
muxers to receive stream parameters from the caller. It is also used
internally as the codec context that is passed to parsers.
In addition, it is also widely used by the callers as the decoding (when
demuxer) or encoding (when muxing) context, though this has been
officially discouraged since Libav 11.
There are multiple important problems with this approach:
- the fields in AVCodecContext are in general one of
* stream parameters
* codec options
* codec state
However, it's not clear which ones are which. It is consequently
unclear which fields are a demuxer allowed to set or a muxer allowed to
read. This leads to erratic behaviour depending on whether decoding or
encoding is being performed or not (and whether it uses the AVStream
embedded codec context).
- various synchronization issues arising from the fact that the same
context is used by several different APIs (muxers/demuxers,
parsers, bitstream filters and encoders/decoders) simultaneously, with
there being no clear rules for who can modify what and the different
processes being typically delayed with respect to each other.
- avformat_find_stream_info() making it necessary to support opening
and closing a single codec context multiple times, thus
complicating the semantics of freeing various allocated objects in the
codec context.
Those problems are resolved by replacing the AVStream embedded codec
context with a newly added AVCodecParameters instance, which stores only
the stream parameters exported by the demuxers or read by the muxers.
2014-06-18 21:42:52 +03:00
|
|
|
stream->codecpar->codec_id = AV_CODEC_ID_PCM_F32BE;
|
2009-04-03 02:53:47 +03:00
|
|
|
#else
|
lavf: replace AVStream.codec with AVStream.codecpar
Currently, AVStream contains an embedded AVCodecContext instance, which
is used by demuxers to export stream parameters to the caller and by
muxers to receive stream parameters from the caller. It is also used
internally as the codec context that is passed to parsers.
In addition, it is also widely used by the callers as the decoding (when
demuxer) or encoding (when muxing) context, though this has been
officially discouraged since Libav 11.
There are multiple important problems with this approach:
- the fields in AVCodecContext are in general one of
* stream parameters
* codec options
* codec state
However, it's not clear which ones are which. It is consequently
unclear which fields are a demuxer allowed to set or a muxer allowed to
read. This leads to erratic behaviour depending on whether decoding or
encoding is being performed or not (and whether it uses the AVStream
embedded codec context).
- various synchronization issues arising from the fact that the same
context is used by several different APIs (muxers/demuxers,
parsers, bitstream filters and encoders/decoders) simultaneously, with
there being no clear rules for who can modify what and the different
processes being typically delayed with respect to each other.
- avformat_find_stream_info() making it necessary to support opening
and closing a single codec context multiple times, thus
complicating the semantics of freeing various allocated objects in the
codec context.
Those problems are resolved by replacing the AVStream embedded codec
context with a newly added AVCodecParameters instance, which stores only
the stream parameters exported by the demuxers or read by the muxers.
2014-06-18 21:42:52 +03:00
|
|
|
stream->codecpar->codec_id = AV_CODEC_ID_PCM_F32LE;
|
2009-04-03 02:53:47 +03:00
|
|
|
#endif
|
lavf: replace AVStream.codec with AVStream.codecpar
Currently, AVStream contains an embedded AVCodecContext instance, which
is used by demuxers to export stream parameters to the caller and by
muxers to receive stream parameters from the caller. It is also used
internally as the codec context that is passed to parsers.
In addition, it is also widely used by the callers as the decoding (when
demuxer) or encoding (when muxing) context, though this has been
officially discouraged since Libav 11.
There are multiple important problems with this approach:
- the fields in AVCodecContext are in general one of
* stream parameters
* codec options
* codec state
However, it's not clear which ones are which. It is consequently
unclear which fields are a demuxer allowed to set or a muxer allowed to
read. This leads to erratic behaviour depending on whether decoding or
encoding is being performed or not (and whether it uses the AVStream
embedded codec context).
- various synchronization issues arising from the fact that the same
context is used by several different APIs (muxers/demuxers,
parsers, bitstream filters and encoders/decoders) simultaneously, with
there being no clear rules for who can modify what and the different
processes being typically delayed with respect to each other.
- avformat_find_stream_info() making it necessary to support opening
and closing a single codec context multiple times, thus
complicating the semantics of freeing various allocated objects in the
codec context.
Those problems are resolved by replacing the AVStream embedded codec
context with a newly added AVCodecParameters instance, which stores only
the stream parameters exported by the demuxers or read by the muxers.
2014-06-18 21:42:52 +03:00
|
|
|
stream->codecpar->sample_rate = self->sample_rate;
|
|
|
|
stream->codecpar->channels = self->nports;
|
2009-04-03 02:53:47 +03:00
|
|
|
|
2011-11-29 21:28:15 +03:00
|
|
|
avpriv_set_pts_info(stream, 64, 1, 1000000); /* 64 bits pts in us */
|
2009-04-03 02:53:47 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-05 15:12:42 +03:00
|
|
|
/* Wait for a packet coming back from process_callback(), if one isn't available yet */
|
2009-04-03 02:53:47 +03:00
|
|
|
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 {
|
2014-10-26 15:02:45 +02:00
|
|
|
char errbuf[128];
|
|
|
|
int ret = AVERROR(errno);
|
|
|
|
av_strerror(ret, errbuf, sizeof(errbuf));
|
2009-04-03 02:53:47 +03:00
|
|
|
av_log(context, AV_LOG_ERROR, "Error while waiting for audio packet: %s\n",
|
2014-10-26 15:02:45 +02:00
|
|
|
errbuf);
|
2009-04-03 02:53:47 +03:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
stop_jack(self);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-07-17 09:08:57 +03:00
|
|
|
#define OFFSET(x) offsetof(JackData, x)
|
|
|
|
static const AVOption options[] = {
|
2012-08-31 13:22:31 +03:00
|
|
|
{ "channels", "Number of audio channels.", OFFSET(nports), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
|
2011-07-17 09:08:57 +03:00
|
|
|
{ NULL },
|
|
|
|
};
|
|
|
|
|
|
|
|
static const AVClass jack_indev_class = {
|
|
|
|
.class_name = "JACK indev",
|
|
|
|
.item_name = av_default_item_name,
|
|
|
|
.option = options,
|
|
|
|
.version = LIBAVUTIL_VERSION_INT,
|
2014-02-23 01:32:51 +03:00
|
|
|
.category = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
|
2011-07-17 09:08:57 +03:00
|
|
|
};
|
|
|
|
|
2011-01-26 00:03:28 +02:00
|
|
|
AVInputFormat ff_jack_demuxer = {
|
2011-09-23 21:50:11 +03:00
|
|
|
.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,
|
2009-04-03 02:53:47 +03:00
|
|
|
};
|