mirror of https://github.com/sipwise/rtpengine.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
11 KiB
416 lines
11 KiB
#include "codecmod.h"
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavformat/avformat.h>
|
|
#include <libavfilter/avfilter.h>
|
|
#include <libavutil/opt.h>
|
|
#include "loglib.h"
|
|
#include "fix_frame_channel_layout.compat"
|
|
|
|
|
|
TYPED_GHASHTABLE(codecs_by_id_alloc, void, struct codec_def_s, g_direct_hash, g_direct_equal, NULL, g_free)
|
|
|
|
|
|
static rwlock_t generic_ffmpeg_codecs_lock = RWLOCK_STATIC_INIT;
|
|
static codecs_by_id_alloc generic_ffmpeg_codecs;
|
|
|
|
|
|
const codec_type_t codec_type_avcodec = {
|
|
.def_init = avc_def_init,
|
|
.decoder_init = avc_decoder_init,
|
|
.decoder_input = avc_decoder_input,
|
|
.decoder_close = avc_decoder_close,
|
|
.encoder_init = avc_encoder_init,
|
|
.encoder_input = avc_encoder_input,
|
|
.encoder_close = avc_encoder_close,
|
|
};
|
|
|
|
|
|
const char *avc_decoder_init(decoder_t *dec, const str *extra_opts) {
|
|
const AVCodec *codec = dec->def->decoder;
|
|
if (!codec)
|
|
return "codec not supported";
|
|
|
|
dec->avc.avpkt = av_packet_alloc();
|
|
|
|
dec->avc.avcctx = avcodec_alloc_context3(codec);
|
|
if (!dec->avc.avcctx)
|
|
return "failed to alloc codec context";
|
|
|
|
if (dec->def->set_dec_options)
|
|
dec->def->set_dec_options(dec, extra_opts);
|
|
|
|
SET_CHANNELS(dec->avc.avcctx, dec->in_format.channels);
|
|
DEF_CH_LAYOUT(&dec->avc.avcctx->CH_LAYOUT, dec->in_format.channels);
|
|
dec->avc.avcctx->sample_rate = dec->in_format.clockrate;
|
|
|
|
int i = avcodec_open2(dec->avc.avcctx, codec, NULL);
|
|
if (i) {
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error returned from libav: %s", av_error(i));
|
|
return "failed to open codec context";
|
|
}
|
|
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 19, 0)
|
|
avcodec_get_supported_config(dec->avc.avcctx, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void **) &dec->avc.sample_fmts, NULL);
|
|
#else
|
|
dec->avc.sample_fmts = codec->sample_fmts;
|
|
#endif
|
|
|
|
for (const enum AVSampleFormat *sfmt = dec->avc.sample_fmts; sfmt && *sfmt != -1; sfmt++)
|
|
ilogs(internals, LOG_DEBUG, "supported sample format for input codec %s: %s",
|
|
codec->name, av_get_sample_fmt_name(*sfmt));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void avc_decoder_close(decoder_t *dec) {
|
|
avcodec_free_context(&dec->avc.avcctx);
|
|
av_packet_free(&dec->avc.avpkt);
|
|
}
|
|
|
|
|
|
int avc_decoder_input(decoder_t *dec, const str *data, frame_q *out, bool mark) {
|
|
if (!dec->avc.avpkt)
|
|
return -1; // decoder shut down
|
|
|
|
const char *err;
|
|
int av_ret = 0;
|
|
|
|
dec->avc.avpkt->data = (unsigned char *) data->s;
|
|
dec->avc.avpkt->size = data->len;
|
|
dec->avc.avpkt->pts = dec->pts;
|
|
|
|
AVFrame *frame = NULL;
|
|
|
|
// loop until all input is consumed and all available output has been processed
|
|
int keep_going;
|
|
do {
|
|
keep_going = 0;
|
|
int got_frame = 0;
|
|
err = "failed to alloc av frame";
|
|
frame = av_frame_alloc();
|
|
if (!frame)
|
|
goto err;
|
|
|
|
if (dec->avc.avpkt->size) {
|
|
av_ret = avcodec_send_packet(dec->avc.avcctx, dec->avc.avpkt);
|
|
ilogs(internals, LOG_DEBUG, "send packet ret %i", av_ret);
|
|
err = "failed to send packet to avcodec";
|
|
if (av_ret == 0) {
|
|
// consumed the packet
|
|
dec->avc.avpkt->size = 0;
|
|
keep_going = 1;
|
|
}
|
|
else {
|
|
if (av_ret == AVERROR(EAGAIN))
|
|
; // try again after reading output
|
|
else
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
av_ret = avcodec_receive_frame(dec->avc.avcctx, frame);
|
|
ilogs(internals, LOG_DEBUG, "receive frame ret %i", av_ret);
|
|
err = "failed to receive frame from avcodec";
|
|
if (av_ret == 0) {
|
|
// got a frame
|
|
keep_going = 1;
|
|
got_frame = 1;
|
|
}
|
|
else {
|
|
if (av_ret == AVERROR(EAGAIN))
|
|
; // maybe needs more input now
|
|
else
|
|
goto err;
|
|
}
|
|
|
|
if (got_frame) {
|
|
ilogs(internals, LOG_DEBUG, "raw frame from decoder pts %llu samples %u",
|
|
(unsigned long long) frame->pts, frame->nb_samples);
|
|
|
|
if (G_UNLIKELY(frame->pts == AV_NOPTS_VALUE))
|
|
frame->pts = dec->avc.avpkt->pts;
|
|
dec->avc.avpkt->pts += frame->nb_samples;
|
|
|
|
t_queue_push_tail(out, frame);
|
|
frame = NULL;
|
|
}
|
|
} while (keep_going);
|
|
|
|
av_frame_free(&frame);
|
|
return 0;
|
|
|
|
err:
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error decoding media packet: %s", err);
|
|
if (av_ret)
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error returned from libav: %s", av_error(av_ret));
|
|
av_frame_free(&frame);
|
|
return -1;
|
|
}
|
|
|
|
|
|
void avc_def_init(struct codec_def_s *def) {
|
|
// look up AVCodec structs
|
|
if (def->avcodec_name_enc)
|
|
def->encoder = avcodec_find_encoder_by_name(def->avcodec_name_enc);
|
|
if (def->avcodec_name_dec)
|
|
def->decoder = avcodec_find_decoder_by_name(def->avcodec_name_dec);
|
|
if (def->avcodec_id >= 0) {
|
|
if (!def->encoder)
|
|
def->encoder = avcodec_find_encoder(def->avcodec_id);
|
|
if (!def->decoder)
|
|
def->decoder = avcodec_find_decoder(def->avcodec_id);
|
|
}
|
|
// check if we have support if we are supposed to
|
|
if (def->avcodec_name_enc || def->avcodec_id >= 0) {
|
|
if (def->encoder)
|
|
def->support_encoding = 1;
|
|
}
|
|
if (def->avcodec_name_dec || def->avcodec_id >= 0) {
|
|
if (def->decoder)
|
|
def->support_decoding = 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const char *avc_encoder_init(encoder_t *enc, const str *extra_opts) {
|
|
enc->avc.codec = enc->def->encoder;
|
|
if (!enc->avc.codec)
|
|
return "output codec not found";
|
|
|
|
enc->avc.avcctx = avcodec_alloc_context3(enc->avc.codec);
|
|
if (!enc->avc.avcctx)
|
|
return "failed to alloc codec context";
|
|
|
|
enc->actual_format = enc->requested_format;
|
|
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 19, 0)
|
|
avcodec_get_supported_config(enc->avc.avcctx, enc->avc.codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void **) &enc->avc.sample_fmts, NULL);
|
|
#else
|
|
enc->avc.sample_fmts = enc->avc.codec->sample_fmts;
|
|
#endif
|
|
|
|
enc->actual_format.format = -1;
|
|
for (const enum AVSampleFormat *sfmt = enc->avc.sample_fmts; sfmt && *sfmt != -1; sfmt++) {
|
|
ilogs(internals, LOG_DEBUG, "supported sample format for output codec %s: %s",
|
|
enc->avc.codec->name, av_get_sample_fmt_name(*sfmt));
|
|
if (*sfmt == enc->requested_format.format)
|
|
enc->actual_format.format = *sfmt;
|
|
}
|
|
if (enc->actual_format.format == -1 && enc->avc.sample_fmts)
|
|
enc->actual_format.format = enc->avc.sample_fmts[0];
|
|
ilogs(internals, LOG_DEBUG, "using output sample format %s for codec %s",
|
|
av_get_sample_fmt_name(enc->actual_format.format), enc->avc.codec->name);
|
|
|
|
if (enc->def->set_enc_options)
|
|
enc->def->set_enc_options(enc, extra_opts);
|
|
|
|
SET_CHANNELS(enc->avc.avcctx, enc->actual_format.channels);
|
|
DEF_CH_LAYOUT(&enc->avc.avcctx->CH_LAYOUT, enc->actual_format.channels);
|
|
enc->avc.avcctx->sample_rate = enc->actual_format.clockrate;
|
|
enc->avc.avcctx->sample_fmt = enc->actual_format.format;
|
|
enc->avc.avcctx->time_base = (AVRational){1,enc->actual_format.clockrate};
|
|
enc->avc.avcctx->bit_rate = enc->bitrate;
|
|
|
|
int i = avcodec_open2(enc->avc.avcctx, enc->avc.codec, NULL);
|
|
if (i) {
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error returned from libav: %s", av_error(i));
|
|
return "failed to open output context";
|
|
}
|
|
|
|
if (enc->avc.avcctx->frame_size)
|
|
enc->samples_per_frame = enc->avc.avcctx->frame_size;
|
|
else
|
|
enc->samples_per_frame = enc->actual_format.clockrate * enc->ptime / 1000;
|
|
|
|
enc->samples_per_packet = enc->samples_per_frame;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void avc_encoder_close(encoder_t *enc) {
|
|
if (enc->avc.avcctx) {
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 0, 0)
|
|
avcodec_close(enc->avc.avcctx);
|
|
#endif
|
|
avcodec_free_context(&enc->avc.avcctx);
|
|
}
|
|
enc->avc.avcctx = NULL;
|
|
enc->avc.codec = NULL;
|
|
}
|
|
|
|
|
|
int avc_encoder_input(encoder_t *enc, AVFrame **frame) {
|
|
int keep_going = 0;
|
|
int got_packet = 0;
|
|
int av_ret = 0;
|
|
|
|
if (!enc->avc.avcctx)
|
|
return -1;
|
|
|
|
if (*frame) {
|
|
av_ret = avcodec_send_frame(enc->avc.avcctx, *frame);
|
|
ilogs(internals, LOG_DEBUG, "send frame ret %i", av_ret);
|
|
if (av_ret == 0) {
|
|
// consumed
|
|
*frame = NULL;
|
|
keep_going = 1;
|
|
}
|
|
else {
|
|
if (av_ret == AVERROR(EAGAIN))
|
|
; // check output and maybe try again
|
|
else
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
av_ret = avcodec_receive_packet(enc->avc.avcctx, enc->avpkt);
|
|
ilogs(internals, LOG_DEBUG, "receive packet ret %i", av_ret);
|
|
if (av_ret == 0) {
|
|
// got some data
|
|
keep_going = 1;
|
|
got_packet = 1;
|
|
}
|
|
else {
|
|
if (av_ret == AVERROR(EAGAIN))
|
|
; // try again if there's still more input
|
|
else
|
|
goto err;
|
|
}
|
|
|
|
if (!got_packet)
|
|
return keep_going;
|
|
|
|
ilogs(internals, LOG_DEBUG, "output avpkt size is %i", (int) enc->avpkt->size);
|
|
ilogs(internals, LOG_DEBUG, "output pkt pts/dts is %li/%li", (long) enc->avpkt->pts,
|
|
(long) enc->avpkt->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;
|
|
|
|
return keep_going;
|
|
|
|
err:
|
|
if (av_ret)
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error returned from libav: %s", av_error(av_ret));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int codeclib_set_av_opt_int(encoder_t *enc, const char *opt, int64_t val) {
|
|
ilog(LOG_DEBUG, "Setting ffmpeg '%s' option for '%s' to %" PRId64,
|
|
opt, enc->def->rtpname, val);
|
|
|
|
int ret = av_opt_set_int(enc->avc.avcctx, opt, val, AV_OPT_SEARCH_CHILDREN);
|
|
if (!ret)
|
|
return 0;
|
|
|
|
ilog(LOG_WARN, "Failed to set ffmpeg '%s' option for codec '%s' to %" PRId64 ": %s",
|
|
opt, enc->def->rtpname, val, av_error(ret));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int codeclib_set_av_opt_intstr(encoder_t *enc, const str *opt, const str *val) {
|
|
g_autoptr(char) s = g_strdup_printf(STR_FORMAT, STR_FMT(opt));
|
|
int i = val ? str_to_i(val, -1) : -1;
|
|
if (i == -1) {
|
|
ilog(LOG_WARN, "Failed to parse '" STR_FORMAT "' as integer value for ffmpeg option '%s'",
|
|
STR_FMT0(val), s);
|
|
return -1;
|
|
}
|
|
return codeclib_set_av_opt_int(enc, s, i);
|
|
}
|
|
|
|
|
|
codec_def_t *codec_def_make_generic_av(enum AVCodecID id, enum media_type type) {
|
|
{
|
|
RWLOCK_R(&generic_ffmpeg_codecs_lock);
|
|
|
|
struct codec_def_s *ret = t_hash_table_lookup(generic_ffmpeg_codecs, GINT_TO_POINTER(id));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
{
|
|
RWLOCK_W(&generic_ffmpeg_codecs_lock);
|
|
|
|
struct codec_def_s *ret = t_hash_table_lookup(generic_ffmpeg_codecs, GINT_TO_POINTER(id));
|
|
if (ret)
|
|
return ret;
|
|
|
|
const AVCodec *codec = avcodec_find_decoder(id);
|
|
if (!codec)
|
|
return NULL;
|
|
|
|
ret = g_new(__typeof(*ret), 1);
|
|
*ret = (__typeof(*ret)) {
|
|
.rtpname = "generic ffmpeg codec",
|
|
.rtpname_str = STR_CONST("generic ffmpeg codec"),
|
|
.avcodec_id = id,
|
|
.default_clockrate_fact = {1,1},
|
|
.media_type = MT_AUDIO,
|
|
.codec_type = &codec_type_avcodec,
|
|
.decoder = codec,
|
|
.support_decoding = 1,
|
|
.support_encoding = 1, // just pretend
|
|
};
|
|
|
|
t_hash_table_insert(generic_ffmpeg_codecs, GINT_TO_POINTER(id), ret);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static void avlog_ilog(void *ptr, int loglevel, const char *fmt, va_list ap) {
|
|
char *msg;
|
|
if (vasprintf(&msg, fmt, ap) <= 0)
|
|
ilogs(ffmpeg, LOG_ERR | LOG_FLAG_LIMIT, "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
|
|
ilogs(ffmpeg, loglevel | LOG_FLAG_LIMIT, "av_log: %s", msg);
|
|
free(msg);
|
|
}
|
|
}
|
|
|
|
|
|
void avc_init(void) {
|
|
avformat_network_init();
|
|
av_log_set_callback(avlog_ilog);
|
|
|
|
generic_ffmpeg_codecs = codecs_by_id_alloc_new();
|
|
}
|
|
|
|
|
|
void avc_cleanup(void) {
|
|
t_hash_table_destroy(generic_ffmpeg_codecs);
|
|
avformat_network_deinit();
|
|
}
|