-
-
Save anonymous/3b68c313ff1a6603d381 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
/* | |
* 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 | |
*/ | |
#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" | |
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); | |
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); | |
if(!self->sample_rate) | |
self->sample_rate = jack_get_sample_rate(self->client); | |
if(!self->ports) | |
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++) { | |
char str[16]; | |
snprintf(str, sizeof(str), "input_%d", i + 1); | |
self->ports[i] = jack_port_register(self->client, str, | |
JACK_DEFAULT_AUDIO_TYPE, | |
self->port_type, 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_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); | |
if (!self->timefilter) { | |
jack_client_close(self->client); | |
return AVERROR(ENOMEM); | |
} | |
/* Create FIFO buffers */ | |
self->filled_pkts = av_fifo_alloc_array(FIFO_PACKETS_NUM, sizeof(AVPacket)); | |
/* New packets FIFO with one extra packet for safety against underruns */ | |
self->new_pkts = av_fifo_alloc_array((FIFO_PACKETS_NUM + 1), sizeof(AVPacket)); | |
if (!self->new_pkts) { | |
jack_client_close(self->client); | |
return AVERROR(ENOMEM); | |
} | |
return 0; | |
} | |
void ff_shutdown_callback(void *arg) | |
{ | |
JackData *self = arg; | |
self->client = NULL; | |
} | |
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) | |
jack_deactivate(self->client); | |
jack_client_close(self->client); | |
} | |
sem_destroy(&self->packet_count); | |
free_pkt_fifo(&self->new_pkts); | |
free_pkt_fifo(&self->filled_pkts); | |
av_freep(&self->ports); | |
ff_timefilter_destroy(self->timefilter); | |
} | |
void free_pkt_fifo(AVFifoBuffer **fifo) | |
{ | |
AVPacket pkt; | |
while (av_fifo_size(*fifo)) { | |
av_fifo_generic_read(*fifo, &pkt, sizeof(pkt), NULL); | |
av_packet_unref(&pkt); | |
} | |
av_fifo_freep(fifo); | |
} |
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
/* | |
* 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 | |
#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 |
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
/* | |
* JACK Audio Connection Kit input device | |
* Copyright (c) 2009 Samalyse | |
* Copyright (c) 2016 Josh de Kock | |
* 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 | |
*/ | |
/** | |
* @file | |
* JACK input and output: input | |
* | |
*/ | |
#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, | |
}; |
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
/* | |
* JACK Audio Connection Kit input device | |
* Copyright (c) 2009 Samalyse | |
* Copyright (c) 2016 Josh de Kock | |
* 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 | |
*/ | |
/** | |
* @file | |
* JACK input and output: input | |
* | |
*/ | |
#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; | |
jack_nframes_t latency; | |
AVPacket pkt; | |
float *pkt_data; | |
if (!self->client) | |
return 0; | |
/* Retrieve filed packet */ | |
av_fifo_generic_read(self->filled_pkts, &pkt, sizeof(pkt), NULL); | |
pkt_data = (float *) pkt.data; | |
latency = 0; | |
/* Copy and interleave audio data from the the packet buffer into JACK */ | |
for (i = 0; i < self->nports; i++) { | |
float * buffer; | |
#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++) | |
buffer[j] = pkt_data[j * self->nports + i]; | |
} | |
/* 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 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; | |
int test; | |
if ((test = ff_start_jack(context))) | |
return test; | |
jack_set_process_callback(self->client, process_callback, self); | |
self->sample_rate = stream->codec->sample_rate; | |
self->nports = stream->codec->channels; | |
return 0; | |
} | |
static int audio_write_packet(AVFormatContext *context, AVPacket *pkt) | |
{ | |
av_log(context, AV_LOG_INFO, "JACK audio_write_packet called"); | |
JackData *self = context->priv_data; | |
// struct timespec timeout = {0, 0}; | |
/* Activate the JACK client on first packet write. 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 writing 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; | |
} | |
/* Write the filled packet filled with audio data by process_callback() */ | |
av_fifo_generic_write(self->filled_pkts, pkt, sizeof(*pkt), NULL); | |
return 0; | |
} | |
static int audio_write_close(AVFormatContext *context) | |
{ | |
av_log(context, AV_LOG_INFO, "JACK audio_write_close called"); | |
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) | |
{ | |
av_log(context, AV_LOG_INFO, "JACK audio_write_frame called"); | |
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) | |
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, | |
.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, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment