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.
rtpengine/lib/avc.c

403 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";
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;
if (dec->def->set_dec_options)
dec->def->set_dec_options(dec, extra_opts);
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);
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;
if (enc->def->set_enc_options)
enc->def->set_enc_options(enc, extra_opts);
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;
}
codec_def_t *codec_def_make_generic_av(enum AVCodecID id) {
{
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();
}