TT#28163 split encoder from recording daemon into shared lib

Change-Id: I76a744b1b1acd32f326d26db7a44273394a4dee1
changes/21/18621/10
Richard Fuchs 8 years ago
parent a4e73c90e8
commit dc80c27af7

@ -15,6 +15,7 @@ struct codec_ssrc_handler {
mutex_t lock;
packet_sequencer_t sequencer;
decoder_t *decoder;
encoder_t *encoder;
};
struct transcode_packet {
seq_packet_t p; // must be first
@ -306,6 +307,12 @@ static struct ssrc_entry *__ssrc_handler_new(void *p) {
ch->decoder = decoder_new_fmt(h->source_pt.codec_def, h->source_pt.clock_rate, 1, 0);
if (!ch->decoder)
goto err;
ch->encoder = encoder_new();
if (!ch->encoder)
goto err;
format_t format = { .clockrate = h->dest_pt.clock_rate, .channels = 1, .format = 0 };
if (encoder_config(ch->encoder, h->dest_pt.codec_def->avcodec_id, 0, &format, &format))
goto err;
return &ch->h;
err:
@ -316,13 +323,26 @@ static void __ssrc_handler_free(struct codec_ssrc_handler *ch) {
packet_sequencer_destroy(&ch->sequencer);
if (ch->decoder)
decoder_close(ch->decoder);
if (ch->encoder)
encoder_free(ch->encoder);
g_slice_free1(sizeof(*ch), ch);
}
int __packet_decoded(decoder_t *decoder, AVFrame *frame, void *u1, void *u2) {
//struct codec_ssrc_handler *ch = u1;
static int __packet_encoded(encoder_t *enc, void *u1, void *u2) {
ilog(LOG_DEBUG, "RTP media successfully encoded: TS %llu, len %i",
(unsigned long long) enc->avpkt.pts, enc->avpkt.size);
return 0;
}
static int __packet_decoded(decoder_t *decoder, AVFrame *frame, void *u1, void *u2) {
struct codec_ssrc_handler *ch = u1;
ilog(LOG_DEBUG, "RTP media successfully decoded: TS %llu, samples %u",
(unsigned long long) frame->pts, frame->nb_samples);
// XXX resample...
ilog(LOG_DEBUG, "RTP media successfully decoded");
encoder_input_data(ch->encoder, frame, __packet_encoded, ch, NULL);
av_frame_free(&frame);
return 0;
@ -338,9 +358,9 @@ static int handler_func_transcode(struct codec_handler *h, struct call_media *me
// create new packet and insert it into sequencer queue
ilog(LOG_DEBUG, "Received RTP packet: SSRC %u, PT %u, seq %u, TS %u",
ilog(LOG_DEBUG, "Received RTP packet: SSRC %u, PT %u, seq %u, TS %u, len %i",
ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num),
ntohl(mp->rtp->timestamp));
ntohl(mp->rtp->timestamp), mp->payload.len);
struct codec_ssrc_handler *ch = get_ssrc(mp->rtp->ssrc, h->ssrc_hash);
if (G_UNLIKELY(!ch))

@ -261,11 +261,42 @@ err:
}
static void avlog_ilog(void *ptr, int loglevel, const char *fmt, va_list ap) {
char *msg;
if (vasprintf(&msg, fmt, ap) <= 0)
ilog(LOG_ERR, "av_log message dropped");
else {
#ifdef AV_LOG_PANIC
// translate AV_LOG_ constants to LOG_ levels
if (loglevel >= AV_LOG_VERBOSE)
loglevel = LOG_DEBUG;
else if (loglevel >= AV_LOG_INFO)
loglevel = LOG_NOTICE;
else if (loglevel >= AV_LOG_WARNING)
loglevel = LOG_WARNING;
else if (loglevel >= AV_LOG_ERROR)
loglevel = LOG_ERROR;
else if (loglevel >= AV_LOG_FATAL)
loglevel = LOG_CRIT;
else
loglevel = LOG_ALERT;
#else
// defuse avlog log levels to be either DEBUG or ERR
if (loglevel <= LOG_ERR)
loglevel = LOG_ERR;
else
loglevel = LOG_DEBUG;
#endif
ilog(loglevel, "av_log: %s", msg);
free(msg);
}
}
void codeclib_init() {
av_register_all();
avcodec_register_all();
avfilter_register_all();
avformat_network_init();
av_log_set_callback(avlog_ilog);
}
@ -386,3 +417,169 @@ seq_ok:
return 0;
}
encoder_t *encoder_new() {
encoder_t *ret = g_slice_alloc0(sizeof(*ret));
format_init(&ret->requested_format);
format_init(&ret->actual_format);
return ret;
}
int encoder_config(encoder_t *enc, int codec_id, int bitrate, const format_t *requested_format,
format_t *actual_format)
{
const char *err;
// anything to do?
if (G_LIKELY(format_eq(requested_format, &enc->requested_format)))
goto done;
encoder_close(enc);
enc->requested_format = *requested_format;
err = "output codec not found";
enc->codec = avcodec_find_encoder(codec_id);
if (!enc->codec)
goto err;
err = "failed to alloc codec context";
enc->avcctx = avcodec_alloc_context3(enc->codec);
if (!enc->avcctx)
goto err;
enc->actual_format = enc->requested_format;
enc->actual_format.format = -1;
for (const enum AVSampleFormat *sfmt = enc->codec->sample_fmts; sfmt && *sfmt != -1; sfmt++) {
dbg("supported sample format for output codec %s: %s", enc->codec->name, av_get_sample_fmt_name(*sfmt));
if (*sfmt == requested_format->format)
enc->actual_format.format = *sfmt;
}
if (enc->actual_format.format == -1 && enc->codec->sample_fmts)
enc->actual_format.format = enc->codec->sample_fmts[0];
dbg("using output sample format %s for codec %s", av_get_sample_fmt_name(enc->actual_format.format), enc->codec->name);
enc->avcctx->channels = enc->actual_format.channels;
enc->avcctx->channel_layout = av_get_default_channel_layout(enc->actual_format.channels);
enc->avcctx->sample_rate = enc->actual_format.clockrate;
enc->avcctx->sample_fmt = enc->actual_format.format;
enc->avcctx->time_base = (AVRational){1,enc->actual_format.clockrate};
enc->avcctx->bit_rate = bitrate;
err = "failed to open output context";
int i = avcodec_open2(enc->avcctx, enc->codec, NULL);
if (i)
goto err;
av_init_packet(&enc->avpkt);
done:
*actual_format = enc->actual_format;
return 0;
err:
encoder_close(enc);
ilog(LOG_ERR, "Error configuring media output: %s", err);
return -1;
}
void encoder_close(encoder_t *enc) {
if (!enc)
return;
if (enc->avcctx) {
avcodec_close(enc->avcctx);
avcodec_free_context(&enc->avcctx);
}
enc->avcctx = NULL;
format_init(&enc->requested_format);
format_init(&enc->actual_format);
enc->mux_dts = 0;
}
void encoder_free(encoder_t *enc) {
encoder_close(enc);
g_slice_free1(sizeof(*enc), enc);
}
int encoder_input_data(encoder_t *enc, AVFrame *frame,
int (*callback)(encoder_t *, void *u1, void *u2), void *u1, void *u2)
{
int keep_going;
int have_frame = 1;
do {
keep_going = 0;
int got_packet = 0;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 36, 0)
if (have_frame) {
int ret = avcodec_send_frame(enc->avcctx, frame);
dbg("send frame ret %i", ret);
if (ret == 0) {
// consumed
have_frame = 0;
keep_going = 1;
}
else {
if (ret == AVERROR(EAGAIN))
; // check output and maybe try again
else
return -1;
}
}
int ret = avcodec_receive_packet(enc->avcctx, &enc->avpkt);
dbg("receive packet ret %i", ret);
if (ret == 0) {
// got some data
keep_going = 1;
got_packet = 1;
}
else {
if (ret == AVERROR(EAGAIN))
; // try again if there's still more input
else
return -1;
}
#else
if (!have_frame)
break;
int ret = avcodec_encode_audio2(enc->avcctx, &enc->avpkt, frame, &got_packet);
dbg("encode frame ret %i, got packet %i", ret, got_packet);
if (ret == 0)
have_frame = 0; // consumed
else
return -1; // error
if (got_packet)
keep_going = 1;
#endif
if (!got_packet)
continue;
// dbg("{%s} output avpkt size is %i", output->file_name, (int) enc->avpkt.size);
// dbg("{%s} output pkt pts/dts is %li/%li", output->file_name, (long) enc->avpkt.pts,
// (long) enc->avpkt.dts);
// dbg("{%s} output dts %li", output->file_name, (long) output->mux_dts);
// the encoder may return frames with the same dts multiple consecutive times.
// the muxer may not like this, so ensure monotonically increasing dts.
if (enc->mux_dts > enc->avpkt.dts)
enc->avpkt.dts = enc->mux_dts;
if (enc->avpkt.pts < enc->avpkt.dts)
enc->avpkt.pts = enc->avpkt.dts;
//av_write_frame(output->fmtctx, &output->avpkt);
callback(enc, u1, u2);
//output->fifo_pts += output->frame->nb_samples;
enc->mux_dts = enc->avpkt.dts + 1; // min next expected dts
av_packet_unref(&enc->avpkt);
} while (keep_going);
return 0;
}

@ -10,6 +10,7 @@
struct codec_def_s;
struct decoder_s;
struct encoder_s;
struct format_s;
struct resample_s;
struct seq_packet_s;
@ -17,6 +18,7 @@ struct packet_sequencer_s;
typedef struct codec_def_s codec_def_t;
typedef struct decoder_s decoder_t;
typedef struct encoder_s encoder_t;
typedef struct format_s format_t;
typedef struct resample_s resample_t;
typedef struct seq_packet_s seq_packet_t;
@ -56,6 +58,16 @@ struct decoder_s {
unsigned int mixer_idx;
};
struct encoder_s {
format_t requested_format,
actual_format;
AVCodec *codec;
AVCodecContext *avcctx;
AVPacket avpkt;
int64_t mux_dts; // last dts passed to muxer
};
struct seq_packet_s {
int seq;
};
@ -78,6 +90,14 @@ int decoder_input_data(decoder_t *dec, const str *data, unsigned long ts,
int (*callback)(decoder_t *, AVFrame *, void *u1, void *u2), void *u1, void *u2);
encoder_t *encoder_new();
int encoder_config(encoder_t *enc, int codec_id, int bitrate,
const format_t *requested_format, format_t *actual_format);
void encoder_close(encoder_t *);
void encoder_free(encoder_t *);
int encoder_input_data(encoder_t *enc, AVFrame *frame,
int (*callback)(encoder_t *, void *u1, void *u2), void *u1, void *u2);
void packet_sequencer_init(packet_sequencer_t *ps, GDestroyNotify);
void packet_sequencer_destroy(packet_sequencer_t *ps);

@ -18,6 +18,7 @@ flags = [
'-fno-strict-aliasing',
'-I/usr/include/glib-2.0',
'-I/usr/lib/x86_64-linux-gnu/glib-2.0/include',
'-I/usr/include/mysql',
'-I../lib/',
'-pthread',
'-D_GNU_SOURCE',

@ -317,8 +317,8 @@ void db_config_stream(output_t *op) {
return;
MYSQL_BIND b[3];
my_i(&b[0], &op->actual_format.channels);
my_i(&b[1], &op->actual_format.clockrate);
my_i(&b[0], &op->encoder->actual_format.channels);
my_i(&b[1], &op->encoder->actual_format.clockrate);
my_ull(&b[2], &op->db_id);
execute_wrap(&stm_config_stream, b, NULL);

@ -62,43 +62,10 @@ static void signals(void) {
}
static void avlog_ilog(void *ptr, int loglevel, const char *fmt, va_list ap) {
char *msg;
if (vasprintf(&msg, fmt, ap) <= 0)
ilog(LOG_ERR, "av_log message dropped");
else {
#ifdef AV_LOG_PANIC
// translate AV_LOG_ constants to LOG_ levels
if (loglevel >= AV_LOG_VERBOSE)
loglevel = LOG_DEBUG;
else if (loglevel >= AV_LOG_INFO)
loglevel = LOG_NOTICE;
else if (loglevel >= AV_LOG_WARNING)
loglevel = LOG_WARNING;
else if (loglevel >= AV_LOG_ERROR)
loglevel = LOG_ERROR;
else if (loglevel >= AV_LOG_FATAL)
loglevel = LOG_CRIT;
else
loglevel = LOG_ALERT;
#else
// defuse avlog log levels to be either DEBUG or ERR
if (loglevel <= LOG_ERR)
loglevel = LOG_ERR;
else
loglevel = LOG_DEBUG;
#endif
ilog(loglevel, "av_log: %s", msg);
free(msg);
}
}
static void setup(void) {
log_init("rtpengine-recording");
if (output_enabled) {
codeclib_init();
av_log_set_callback(avlog_ilog);
output_init(output_format);
if (!g_file_test(output_dir, G_FILE_TEST_IS_DIR)) {
ilog(LOG_INFO, "Creating output dir '%s'", output_dir);

@ -19,6 +19,22 @@ static void output_shutdown(output_t *output);
static int output_got_packet(encoder_t *enc, void *u1, void *u2) {
output_t *output = u1;
dbg("{%s} output avpkt size is %i", output->file_name, (int) enc->avpkt.size);
dbg("{%s} output pkt pts/dts is %li/%li", output->file_name, (long) enc->avpkt.pts,
(long) enc->avpkt.dts);
dbg("{%s} output dts %li", output->file_name, (long) output->encoder->mux_dts);
av_write_frame(output->fmtctx, &enc->avpkt);
output->fifo_pts += output->frame->nb_samples;
return 0;
}
static int output_flush(output_t *output) {
while (av_audio_fifo_size(output->fifo) >= output->frame->nb_samples) {
@ -29,78 +45,7 @@ static int output_flush(output_t *output) {
dbg("{%s} output fifo pts %lu", output->file_name, (unsigned long) output->fifo_pts);
output->frame->pts = output->fifo_pts;
int keep_going;
int have_frame = 1;
do {
keep_going = 0;
int got_packet = 0;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 36, 0)
if (have_frame) {
int ret = avcodec_send_frame(output->avcctx, output->frame);
dbg("{%s} send frame ret %i", output->file_name, ret);
if (ret == 0) {
// consumed
have_frame = 0;
keep_going = 1;
}
else {
if (ret == AVERROR(EAGAIN))
; // check output and maybe try again
else
return -1;
}
}
int ret = avcodec_receive_packet(output->avcctx, &output->avpkt);
dbg("{%s} receive packet ret %i", output->file_name, ret);
if (ret == 0) {
// got some data
keep_going = 1;
got_packet = 1;
}
else {
if (ret == AVERROR(EAGAIN))
; // try again if there's still more input
else
return -1;
}
#else
if (!have_frame)
break;
int ret = avcodec_encode_audio2(output->avcctx, &output->avpkt, output->frame, &got_packet);
dbg("{%s} encode frame ret %i, got packet %i", output->file_name, ret, got_packet);
if (ret == 0)
have_frame = 0; // consumed
else
return -1; // error
if (got_packet)
keep_going = 1;
#endif
if (!got_packet)
continue;
dbg("{%s} output avpkt size is %i", output->file_name, (int) output->avpkt.size);
dbg("{%s} output pkt pts/dts is %li/%li", output->file_name, (long) output->avpkt.pts,
(long) output->avpkt.dts);
dbg("{%s} output dts %li", output->file_name, (long) output->mux_dts);
// the encoder may return frames with the same dts multiple consecutive times.
// the muxer may not like this, so ensure monotonically increasing dts.
if (output->mux_dts > output->avpkt.dts)
output->avpkt.dts = output->mux_dts;
if (output->avpkt.pts < output->avpkt.dts)
output->avpkt.pts = output->avpkt.dts;
av_write_frame(output->fmtctx, &output->avpkt);
output->fifo_pts += output->frame->nb_samples;
output->mux_dts = output->avpkt.dts + 1; // min next expected dts
av_packet_unref(&output->avpkt);
} while (keep_going);
encoder_input_data(output->encoder, output->frame, output_got_packet, output, NULL);
}
return 0;
@ -131,9 +76,8 @@ output_t *output_new(const char *path, const char *filename) {
g_strlcpy(ret->file_path, path, sizeof(ret->file_path));
g_strlcpy(ret->file_name, filename, sizeof(ret->file_name));
snprintf(ret->full_filename, sizeof(ret->full_filename), "%s/%s", path, filename);
format_init(&ret->requested_format);
format_init(&ret->actual_format);
ret->file_format = output_file_format;
ret->encoder = encoder_new();
return ret;
}
@ -141,13 +85,13 @@ output_t *output_new(const char *path, const char *filename) {
int output_config(output_t *output, const format_t *requested_format, format_t *actual_format) {
const char *err;
// anything to do?
if (G_LIKELY(format_eq(requested_format, &output->requested_format)))
goto done;
// // anything to do?
// if (G_LIKELY(format_eq(requested_format, &output->requested_format)))
// goto done;
output_shutdown(output);
output->requested_format = *requested_format;
// output->requested_format = *requested_format;
err = "failed to alloc format context";
output->fmtctx = avformat_alloc_context();
@ -158,47 +102,50 @@ int output_config(output_t *output, const format_t *requested_format, format_t *
if (!output->fmtctx->oformat)
goto err;
err = "output codec not found";
AVCodec *codec = avcodec_find_encoder(output_codec_id);
if (!codec)
if (encoder_config(output->encoder, output_codec_id, mp3_bitrate, requested_format, actual_format))
goto err;
// err = "output codec not found";
// AVCodec *codec = avcodec_find_encoder(output_codec_id);
// if (!codec)
// goto err;
err = "failed to alloc output stream";
output->avst = avformat_new_stream(output->fmtctx, codec);
output->avst = avformat_new_stream(output->fmtctx, output->encoder->codec);
if (!output->avst)
goto err;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
err = "failed to alloc codec context";
output->avcctx = avcodec_alloc_context3(codec);
if (!output->avcctx)
goto err;
#else
output->avcctx = output->avst->codec;
#endif
//#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
// err = "failed to alloc codec context";
// output->avcctx = avcodec_alloc_context3(codec);
// if (!output->avcctx)
// goto err;
//#else
// output->avcctx = output->avst->codec;
//#endif
// copy all format params
output->actual_format = output->requested_format;
// output->actual_format = output->requested_format;
// determine sample format to use
output->actual_format.format = -1;
for (const enum AVSampleFormat *sfmt = codec->sample_fmts; sfmt && *sfmt != -1; sfmt++) {
dbg("supported sample format for output codec %s: %s", codec->name, av_get_sample_fmt_name(*sfmt));
if (*sfmt == requested_format->format)
output->actual_format.format = *sfmt;
}
if (output->actual_format.format == -1 && codec->sample_fmts)
output->actual_format.format = codec->sample_fmts[0];
dbg("using output sample format %s for codec %s", av_get_sample_fmt_name(output->actual_format.format), codec->name);
output->avcctx->channels = output->actual_format.channels;
output->avcctx->channel_layout = av_get_default_channel_layout(output->actual_format.channels);
output->avcctx->sample_rate = output->actual_format.clockrate;
output->avcctx->sample_fmt = output->actual_format.format;
output->avcctx->time_base = (AVRational){1,output->actual_format.clockrate};
output->avcctx->bit_rate = mp3_bitrate;
output->avst->time_base = output->avcctx->time_base;
// output->actual_format.format = -1;
// for (const enum AVSampleFormat *sfmt = codec->sample_fmts; sfmt && *sfmt != -1; sfmt++) {
// dbg("supported sample format for output codec %s: %s", codec->name, av_get_sample_fmt_name(*sfmt));
// if (*sfmt == requested_format->format)
// output->actual_format.format = *sfmt;
// }
// if (output->actual_format.format == -1 && codec->sample_fmts)
// output->actual_format.format = codec->sample_fmts[0];
// dbg("using output sample format %s for codec %s", av_get_sample_fmt_name(output->actual_format.format), codec->name);
//
// output->avcctx->channels = output->actual_format.channels;
// output->avcctx->channel_layout = av_get_default_channel_layout(output->actual_format.channels);
// output->avcctx->sample_rate = output->actual_format.clockrate;
// output->avcctx->sample_fmt = output->actual_format.format;
// output->avcctx->time_base = (AVRational){1,output->actual_format.clockrate};
// output->avcctx->bit_rate = mp3_bitrate;
output->avst->time_base = output->encoder->avcctx->time_base;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 26, 0) // exact version? present in 57.56
avcodec_parameters_from_context(output->avst->codecpar, output->avcctx);
avcodec_parameters_from_context(output->avst->codecpar, output->encoder->avcctx);
#endif
char full_fn[PATH_MAX*2];
@ -214,11 +161,12 @@ int output_config(output_t *output, const format_t *requested_format, format_t *
goto err;
got_fn:
err = "failed to open output context";
int i = avcodec_open2(output->avcctx, codec, NULL);
if (i)
goto err;
// err = "failed to open output context";
// int i = avcodec_open2(output->avcctx, codec, NULL);
// if (i)
// goto err;
err = "failed to open avio";
int i;
i = avio_open(&output->fmtctx->pb, full_fn, AVIO_FLAG_WRITE);
if (i < 0)
goto err;
@ -227,24 +175,22 @@ got_fn:
if (i)
goto err;
av_init_packet(&output->avpkt);
// av_init_packet(&output->avpkt);
// output frame and fifo
output->frame = av_frame_alloc();
output->frame->nb_samples = output->avcctx->frame_size ? : 256;
output->frame->format = output->avcctx->sample_fmt;
output->frame->sample_rate = output->avcctx->sample_rate;
output->frame->channel_layout = output->avcctx->channel_layout;
output->frame->nb_samples = output->encoder->avcctx->frame_size ? : 256;
output->frame->format = output->encoder->avcctx->sample_fmt;
output->frame->sample_rate = output->encoder->avcctx->sample_rate;
output->frame->channel_layout = output->encoder->avcctx->channel_layout;
if (!output->frame->channel_layout)
output->frame->channel_layout = av_get_default_channel_layout(output->avcctx->channels);
output->frame->channel_layout = av_get_default_channel_layout(output->encoder->avcctx->channels);
if (av_frame_get_buffer(output->frame, 0) < 0)
abort();
output->fifo = av_audio_fifo_alloc(output->avcctx->sample_fmt, output->avcctx->channels,
output->fifo = av_audio_fifo_alloc(output->encoder->avcctx->sample_fmt, output->encoder->avcctx->channels,
output->frame->nb_samples);
done:
*actual_format = output->actual_format;
db_config_stream(output);
return 0;
@ -265,24 +211,25 @@ static void output_shutdown(output_t *output) {
av_write_trailer(output->fmtctx);
avio_closep(&output->fmtctx->pb);
}
avcodec_close(output->avcctx);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
avcodec_free_context(&output->avcctx);
#endif
// avcodec_close(output->avcctx);
//#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
// avcodec_free_context(&output->avcctx);
//#endif
avformat_free_context(output->fmtctx);
av_audio_fifo_free(output->fifo);
av_frame_free(&output->frame);
output->avcctx = NULL;
encoder_close(output->encoder);
// output->avcctx = NULL;
output->fmtctx = NULL;
output->avst = NULL;
output->fifo = NULL;
output->fifo_pts = 0;
output->mux_dts = 0;
format_init(&output->requested_format);
format_init(&output->actual_format);
// format_init(&output->requested_format);
// format_init(&output->actual_format);
}
@ -291,6 +238,7 @@ void output_close(output_t *output) {
return;
output_shutdown(output);
db_close_stream(output);
encoder_free(output->encoder);
g_slice_free1(sizeof(*output), output);
}

@ -110,17 +110,18 @@ struct output_s {
const char *file_format;
unsigned long long db_id;
format_t requested_format,
actual_format;
// format_t requested_format,
// actual_format;
AVCodecContext *avcctx;
// AVCodecContext *avcctx;
AVFormatContext *fmtctx;
AVStream *avst;
AVPacket avpkt;
// AVPacket avpkt;
AVAudioFifo *fifo;
int64_t fifo_pts; // pts of first data in fifo
int64_t mux_dts; // last dts passed to muxer
// int64_t mux_dts; // last dts passed to muxer
AVFrame *frame;
encoder_t *encoder;
};

Loading…
Cancel
Save