mirror of
https://github.com/livepeer/lpms
synced 2025-11-02 04:22:58 +08:00
883 lines
31 KiB
C
883 lines
31 KiB
C
#include "lpms_ffmpeg.h"
|
|
|
|
#include <libavformat/avformat.h>
|
|
#include <libavfilter/avfilter.h>
|
|
#include <libavfilter/buffersink.h>
|
|
#include <libavfilter/buffersrc.h>
|
|
#include <libavutil/opt.h>
|
|
|
|
// Not great to appropriate internal API like this...
|
|
const int lpms_ERR_INPUT_PIXFMT = FFERRTAG('I','N','P','X');
|
|
const int lpms_ERR_FILTERS = FFERRTAG('F','L','T','R');
|
|
|
|
//
|
|
// Internal transcoder data structures
|
|
//
|
|
struct input_ctx {
|
|
AVFormatContext *ic; // demuxer required
|
|
AVCodecContext *vc; // video decoder optional
|
|
AVCodecContext *ac; // audo decoder optional
|
|
int vi, ai; // video and audio stream indices
|
|
|
|
// Hardware decoding support
|
|
AVBufferRef *hw_device_ctx;
|
|
enum AVHWDeviceType hw_type;
|
|
};
|
|
|
|
struct filter_ctx {
|
|
int active;
|
|
AVFilterGraph *graph;
|
|
AVFrame *frame;
|
|
AVFilterContext *sink_ctx;
|
|
AVFilterContext *src_ctx;
|
|
|
|
uint8_t *hwframes; // GPU frame pool data
|
|
};
|
|
|
|
struct output_ctx {
|
|
char *fname; // required output file name
|
|
char *vencoder; // required output video encoder
|
|
char *vfilters; // required output video filters
|
|
int width, height, bitrate; // w, h, br required
|
|
AVRational fps;
|
|
AVFormatContext *oc; // muxer required
|
|
AVCodecContext *vc; // video decoder optional
|
|
AVCodecContext *ac; // audo decoder optional
|
|
int vi, ai; // video and audio stream indices
|
|
struct filter_ctx vf, af;
|
|
|
|
int64_t drop_ts; // preroll audio ts to drop
|
|
|
|
output_results *res; // data to return for this output
|
|
};
|
|
|
|
void lpms_init()
|
|
{
|
|
av_log_set_level(AV_LOG_WARNING);
|
|
}
|
|
|
|
//
|
|
// Segmenter
|
|
//
|
|
|
|
int lpms_rtmp2hls(char *listen, char *outf, char *ts_tmpl, char* seg_time, char *seg_start)
|
|
{
|
|
#define r2h_err(str) {\
|
|
if (!ret) ret = 1; \
|
|
errstr = str; \
|
|
goto handle_r2h_err; \
|
|
}
|
|
char *errstr = NULL;
|
|
int ret = 0;
|
|
AVFormatContext *ic = NULL;
|
|
AVFormatContext *oc = NULL;
|
|
AVOutputFormat *ofmt = NULL;
|
|
AVStream *ist = NULL;
|
|
AVStream *ost = NULL;
|
|
AVDictionary *md = NULL;
|
|
AVCodec *codec = NULL;
|
|
int64_t prev_ts[2] = {AV_NOPTS_VALUE, AV_NOPTS_VALUE};
|
|
int stream_map[2] = {-1, -1};
|
|
int got_video_kf = 0;
|
|
AVPacket pkt;
|
|
|
|
ret = avformat_open_input(&ic, listen, NULL, NULL);
|
|
if (ret < 0) r2h_err("segmenter: Unable to open input\n");
|
|
ret = avformat_find_stream_info(ic, NULL);
|
|
if (ret < 0) r2h_err("segmenter: Unable to find any input streams\n");
|
|
|
|
ofmt = av_guess_format(NULL, outf, NULL);
|
|
if (!ofmt) r2h_err("Could not deduce output format from file extension\n");
|
|
ret = avformat_alloc_output_context2(&oc, ofmt, NULL, outf);
|
|
if (ret < 0) r2h_err("Unable to allocate output context\n");
|
|
|
|
// XXX accommodate cases where audio or video is empty
|
|
stream_map[0] = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
|
if (stream_map[0] < 0) r2h_err("segmenter: Unable to find video stream\n");
|
|
stream_map[1] = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
|
|
if (stream_map[1] < 0) r2h_err("segmenter: Unable to find audio stream\n");
|
|
|
|
ist = ic->streams[stream_map[0]];
|
|
ost = avformat_new_stream(oc, NULL);
|
|
if (!ost) r2h_err("segmenter: Unable to allocate output video stream\n");
|
|
avcodec_parameters_copy(ost->codecpar, ist->codecpar);
|
|
ist = ic->streams[stream_map[1]];
|
|
ost = avformat_new_stream(oc, NULL);
|
|
if (!ost) r2h_err("segmenter: Unable to allocate output audio stream\n");
|
|
avcodec_parameters_copy(ost->codecpar, ist->codecpar);
|
|
|
|
av_dict_set(&md, "hls_time", seg_time, 0);
|
|
av_dict_set(&md, "hls_segment_filename", ts_tmpl, 0);
|
|
av_dict_set(&md, "start_number", seg_start, 0);
|
|
av_dict_set(&md, "hls_flags", "delete_segments", 0);
|
|
ret = avformat_write_header(oc, &md);
|
|
if (ret < 0) r2h_err("Error writing header\n");
|
|
|
|
av_init_packet(&pkt);
|
|
while (1) {
|
|
ret = av_read_frame(ic, &pkt);
|
|
if (ret == AVERROR_EOF) {
|
|
av_interleaved_write_frame(oc, NULL); // flush
|
|
break;
|
|
} else if (ret < 0) r2h_err("Error reading\n");
|
|
// rescale timestamps
|
|
if (pkt.stream_index == stream_map[0]) pkt.stream_index = 0;
|
|
else if (pkt.stream_index == stream_map[1]) pkt.stream_index = 1;
|
|
else goto r2hloop_end;
|
|
ist = ic->streams[stream_map[pkt.stream_index]];
|
|
ost = oc->streams[pkt.stream_index];
|
|
int64_t dts_next = pkt.dts, dts_prev = prev_ts[pkt.stream_index];
|
|
if (oc->streams[pkt.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
|
|
AV_NOPTS_VALUE == dts_prev &&
|
|
(pkt.flags & AV_PKT_FLAG_KEY)) got_video_kf = 1;
|
|
if (!got_video_kf) goto r2hloop_end; // skip everyting until first video KF
|
|
if (AV_NOPTS_VALUE == dts_prev) dts_prev = dts_next;
|
|
else if (dts_next <= dts_prev) goto r2hloop_end; // drop late packets
|
|
pkt.pts = av_rescale_q_rnd(pkt.pts, ist->time_base, ost->time_base,
|
|
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
|
|
pkt.dts = av_rescale_q_rnd(pkt.dts, ist->time_base, ost->time_base,
|
|
AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
|
|
if (!pkt.duration) pkt.duration = dts_next - dts_prev;
|
|
pkt.duration = av_rescale_q(pkt.duration, ist->time_base, ost->time_base);
|
|
prev_ts[pkt.stream_index] = dts_next;
|
|
// write the thing
|
|
ret = av_interleaved_write_frame(oc, &pkt);
|
|
if (ret < 0) r2h_err("segmenter: Unable to write output frame\n");
|
|
r2hloop_end:
|
|
av_packet_unref(&pkt);
|
|
}
|
|
ret = av_write_trailer(oc);
|
|
if (ret < 0) r2h_err("segmenter: Unable to write trailer\n");
|
|
|
|
handle_r2h_err:
|
|
if (errstr) fprintf(stderr, "%s", errstr);
|
|
if (ic) avformat_close_input(&ic);
|
|
if (oc) avformat_free_context(oc);
|
|
if (md) av_dict_free(&md);
|
|
return ret == AVERROR_EOF ? 0 : ret;
|
|
}
|
|
|
|
//
|
|
// Transcoder
|
|
//
|
|
|
|
static void free_filter(struct filter_ctx *filter)
|
|
{
|
|
if (filter->frame) av_frame_free(&filter->frame);
|
|
if (filter->graph) avfilter_graph_free(&filter->graph);
|
|
}
|
|
|
|
static void free_output(struct output_ctx *octx)
|
|
{
|
|
if (octx->oc) {
|
|
if (!(octx->oc->oformat->flags & AVFMT_NOFILE) && octx->oc->pb) {
|
|
avio_closep(&octx->oc->pb);
|
|
}
|
|
avformat_free_context(octx->oc);
|
|
octx->oc = NULL;
|
|
}
|
|
if (octx->vc) avcodec_free_context(&octx->vc);
|
|
if (octx->ac) avcodec_free_context(&octx->ac);
|
|
free_filter(&octx->vf);
|
|
free_filter(&octx->af);
|
|
}
|
|
|
|
static enum AVPixelFormat hw2pixfmt(AVCodecContext *ctx)
|
|
{
|
|
const AVCodec *decoder = ctx->codec;
|
|
struct input_ctx *params = (struct input_ctx*)ctx->opaque;
|
|
for (int i = 0;; i++) {
|
|
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
|
if (!config) {
|
|
fprintf(stderr, "Decoder %s does not support hw decoding\n", decoder->name);
|
|
return AV_PIX_FMT_NONE;
|
|
}
|
|
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
|
|
config->device_type == params->hw_type) {
|
|
return config->pix_fmt;
|
|
}
|
|
}
|
|
return AV_PIX_FMT_NONE;
|
|
}
|
|
|
|
static enum AVPixelFormat get_hw_pixfmt(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts)
|
|
{
|
|
// XXX see avcodec_get_hw_frames_parameters if fmt changes mid-stream
|
|
return hw2pixfmt(ctx);
|
|
}
|
|
|
|
static int open_output(struct output_ctx *octx, struct input_ctx *ictx)
|
|
{
|
|
#define em_err(msg) { \
|
|
if (!ret) ret = -1; \
|
|
fprintf(stderr, msg); \
|
|
goto open_output_err; \
|
|
}
|
|
int ret = 0;
|
|
AVOutputFormat *fmt = NULL;
|
|
AVFormatContext *oc = NULL;
|
|
AVCodecContext *vc = NULL;
|
|
AVCodecContext *ac = NULL;
|
|
AVCodec *codec = NULL;
|
|
AVStream *st = NULL;
|
|
|
|
// open muxer
|
|
fmt = av_guess_format(NULL, octx->fname, NULL);
|
|
if (!fmt) em_err("Unable to guess output format\n");
|
|
ret = avformat_alloc_output_context2(&oc, fmt, NULL, octx->fname);
|
|
if (ret < 0) em_err("Unable to alloc output context\n");
|
|
octx->oc = oc;
|
|
|
|
if (ictx->vc) {
|
|
codec = avcodec_find_encoder_by_name(octx->vencoder);
|
|
if (!codec) em_err("Unable to find encoder");
|
|
|
|
// open video encoder
|
|
// XXX use avoptions rather than manual enumeration
|
|
vc = avcodec_alloc_context3(codec);
|
|
if (!vc) em_err("Unable to alloc video encoder\n");
|
|
octx->vc = vc;
|
|
vc->width = av_buffersink_get_w(octx->vf.sink_ctx);
|
|
vc->height = av_buffersink_get_h(octx->vf.sink_ctx);
|
|
if (octx->fps.den) vc->framerate = av_buffersink_get_frame_rate(octx->vf.sink_ctx);
|
|
if (octx->fps.den) vc->time_base = av_buffersink_get_time_base(octx->vf.sink_ctx);
|
|
if (octx->bitrate) vc->rc_min_rate = vc->rc_max_rate = vc->rc_buffer_size = octx->bitrate;
|
|
if (av_buffersink_get_hw_frames_ctx(octx->vf.sink_ctx)) {
|
|
vc->hw_frames_ctx =
|
|
av_buffer_ref(av_buffersink_get_hw_frames_ctx(octx->vf.sink_ctx));
|
|
}
|
|
vc->pix_fmt = av_buffersink_get_format(octx->vf.sink_ctx); // XXX select based on encoder + input support
|
|
if (fmt->flags & AVFMT_GLOBALHEADER) vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
|
/*
|
|
if (ictx->vc->extradata) {
|
|
// XXX only if transmuxing!
|
|
vc->extradata = av_mallocz(ictx->vc->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
|
if (!vc->extradata) em_err("Unable to allocate video extradata\n");
|
|
memcpy(vc->extradata, ictx->vc->extradata, ictx->vc->extradata_size);
|
|
vc->extradata_size = ictx->vc->extradata_size;
|
|
}*/
|
|
ret = avcodec_open2(vc, codec, NULL);
|
|
if (ret < 0) em_err("Error opening video encoder\n");
|
|
|
|
// video stream in muxer
|
|
st = avformat_new_stream(oc, NULL);
|
|
if (!st) em_err("Unable to alloc video stream\n");
|
|
octx->vi = st->index;
|
|
st->avg_frame_rate = octx->fps;
|
|
st->time_base = vc->time_base;
|
|
ret = avcodec_parameters_from_context(st->codecpar, vc);
|
|
if (ret < 0) em_err("Error setting video encoder params\n");
|
|
}
|
|
|
|
if (ictx->ac) {
|
|
codec = avcodec_find_encoder_by_name("aac"); // XXX make more flexible?
|
|
if (!codec) em_err("Unable to find aac\n");
|
|
// open audio encoder
|
|
ac = avcodec_alloc_context3(codec);
|
|
if (!ac) em_err("Unable to alloc audio encoder\n"); // XXX shld be optional
|
|
octx->ac = ac;
|
|
ac->sample_fmt = av_buffersink_get_format(octx->af.sink_ctx);
|
|
ac->channel_layout = av_buffersink_get_channel_layout(octx->af.sink_ctx);
|
|
ac->channels = av_buffersink_get_channels(octx->af.sink_ctx);
|
|
ac->sample_rate = av_buffersink_get_sample_rate(octx->af.sink_ctx);
|
|
ac->time_base = av_buffersink_get_time_base(octx->af.sink_ctx);
|
|
if (fmt->flags & AVFMT_GLOBALHEADER) ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
|
ret = avcodec_open2(ac, codec, NULL);
|
|
if (ret < 0) em_err("Error opening audio encoder\n");
|
|
av_buffersink_set_frame_size(octx->af.sink_ctx, ac->frame_size);
|
|
|
|
// audio stream in muxer
|
|
st = avformat_new_stream(oc, NULL);
|
|
if (!st) em_err("Unable to alloc audio stream\n");
|
|
ret = avcodec_parameters_from_context(st->codecpar, ac);
|
|
st->time_base = ac->time_base;
|
|
if (ret < 0) em_err("Unable to copy audio codec params\n");
|
|
octx->ai = st->index;
|
|
|
|
// signal whether to drop preroll audio
|
|
if (st->codecpar->initial_padding) octx->drop_ts = AV_NOPTS_VALUE;
|
|
}
|
|
|
|
if (!(fmt->flags & AVFMT_NOFILE)) {
|
|
ret = avio_open(&octx->oc->pb, octx->fname, AVIO_FLAG_WRITE);
|
|
if (ret < 0) em_err("Error opening output file\n");
|
|
}
|
|
|
|
ret = avformat_write_header(oc, NULL);
|
|
if (ret < 0) em_err("Error writing header\n");
|
|
|
|
return 0;
|
|
|
|
open_output_err:
|
|
free_output(octx);
|
|
return ret;
|
|
}
|
|
|
|
static void free_input(struct input_ctx *inctx)
|
|
{
|
|
if (inctx->ic) avformat_close_input(&inctx->ic);
|
|
if (inctx->vc) avcodec_free_context(&inctx->vc);
|
|
if (inctx->ac) avcodec_free_context(&inctx->ac);
|
|
if (inctx->hw_device_ctx) av_buffer_unref(&inctx->hw_device_ctx);
|
|
}
|
|
|
|
static int open_input(input_params *params, struct input_ctx *ctx)
|
|
{
|
|
#define dd_err(msg) { \
|
|
if (!ret) ret = -1; \
|
|
fprintf(stderr, msg); \
|
|
goto open_input_err; \
|
|
}
|
|
AVCodec *codec = NULL;
|
|
AVFormatContext *ic = NULL;
|
|
char *inp = params->fname;
|
|
|
|
// open demuxer
|
|
int ret = avformat_open_input(&ic, inp, NULL, NULL);
|
|
if (ret < 0) dd_err("demuxer: Unable to open input\n");
|
|
ctx->ic = ic;
|
|
ret = avformat_find_stream_info(ic, NULL);
|
|
if (ret < 0) dd_err("Unable to find input info\n");
|
|
|
|
// open video decoder
|
|
ctx->vi = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
|
if (ctx->vi < 0) {
|
|
fprintf(stderr, "No video stream found in input\n");
|
|
} else {
|
|
AVCodecContext *vc = avcodec_alloc_context3(codec);
|
|
if (!vc) dd_err("Unable to alloc video codec\n");
|
|
ctx->vc = vc;
|
|
ret = avcodec_parameters_to_context(vc, ic->streams[ctx->vi]->codecpar);
|
|
if (ret < 0) dd_err("Unable to assign video params\n");
|
|
if (params->hw_type != AV_HWDEVICE_TYPE_NONE) {
|
|
// First set the hw device then set the hw frame
|
|
AVHWFramesContext *frames;
|
|
ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, params->hw_type, params->device, NULL, 0);
|
|
if (ret < 0) dd_err("Unable to open hardware context for decoding\n")
|
|
ctx->hw_type = params->hw_type;
|
|
vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx);
|
|
vc->get_format = get_hw_pixfmt;
|
|
vc->opaque = (void*)ctx;
|
|
// XXX Ideally this would be auto initialized by the HW device ctx
|
|
// However the initialization doesn't occur in time to set up filters
|
|
// So we do it here. Also see avcodec_get_hw_frames_parameters
|
|
vc->hw_frames_ctx = av_hwframe_ctx_alloc(vc->hw_device_ctx);
|
|
if (!vc->hw_frames_ctx) dd_err("Unable to allocate hwframe context for decoding\n")
|
|
frames = (AVHWFramesContext*)vc->hw_frames_ctx->data;
|
|
frames->format = hw2pixfmt(vc);
|
|
frames->sw_format = vc->pix_fmt;
|
|
frames->width = vc->width;
|
|
frames->height = vc->height;
|
|
|
|
// May want to allocate extra HW frames if we encounter samples where
|
|
// the defaults are insufficient. Raising this increases GPU memory usage
|
|
// For now, the defaults seems OK.
|
|
//vc->extra_hw_frames = 16 + 1; // H.264 max refs
|
|
|
|
ret = av_hwframe_ctx_init(vc->hw_frames_ctx);
|
|
if (AVERROR(ENOSYS) == ret) ret = lpms_ERR_INPUT_PIXFMT; // most likely
|
|
if (ret < 0) dd_err("Unable to initialize a hardware frame pool\n")
|
|
}
|
|
ret = avcodec_open2(vc, codec, NULL);
|
|
if (ret < 0) dd_err("Unable to open video decoder\n");
|
|
}
|
|
|
|
// open audio decoder
|
|
ctx->ai = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
|
|
if (ctx->ai < 0) {
|
|
fprintf(stderr, "No audio stream found in input\n");
|
|
} else {
|
|
AVCodecContext * ac = avcodec_alloc_context3(codec);
|
|
if (!ac) dd_err("Unable to alloc audio codec\n");
|
|
ctx->ac = ac;
|
|
ret = avcodec_parameters_to_context(ac, ic->streams[ctx->ai]->codecpar);
|
|
if (ret < 0) dd_err("Unable to assign audio params\n");
|
|
ret = avcodec_open2(ac, codec, NULL);
|
|
if (ret < 0) dd_err("Unable to open audio decoder\n");
|
|
}
|
|
|
|
return 0;
|
|
|
|
open_input_err:
|
|
free_input(ctx);
|
|
return ret;
|
|
#undef dd_err
|
|
}
|
|
|
|
static int init_video_filters(struct input_ctx *ictx, struct output_ctx *octx)
|
|
{
|
|
#define filters_err(msg) { \
|
|
if (!ret) ret = -1; \
|
|
fprintf(stderr, msg); \
|
|
goto init_video_filters_cleanup; \
|
|
}
|
|
char args[512];
|
|
int ret = 0;
|
|
const AVFilter *buffersrc = avfilter_get_by_name("buffer");
|
|
const AVFilter *buffersink = avfilter_get_by_name("buffersink");
|
|
AVFilterInOut *outputs = avfilter_inout_alloc();
|
|
AVFilterInOut *inputs = avfilter_inout_alloc();
|
|
AVRational time_base = ictx->ic->streams[ictx->vi]->time_base;
|
|
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_CUDA, AV_PIX_FMT_NONE }; // XXX ensure the encoder allows this
|
|
struct filter_ctx *vf = &octx->vf;
|
|
char *filters_descr = octx->vfilters;
|
|
enum AVPixelFormat in_pix_fmt = ictx->vc->pix_fmt;
|
|
|
|
vf->graph = avfilter_graph_alloc();
|
|
if (!outputs || !inputs || !vf->graph) {
|
|
ret = AVERROR(ENOMEM);
|
|
filters_err("Unble to allocate filters\n");
|
|
}
|
|
if (ictx->vc->hw_device_ctx) in_pix_fmt = hw2pixfmt(ictx->vc);
|
|
|
|
/* buffer video source: the decoded frames from the decoder will be inserted here. */
|
|
snprintf(args, sizeof args,
|
|
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
|
|
ictx->vc->width, ictx->vc->height, in_pix_fmt,
|
|
time_base.num, time_base.den,
|
|
ictx->vc->sample_aspect_ratio.num, ictx->vc->sample_aspect_ratio.den);
|
|
|
|
ret = avfilter_graph_create_filter(&vf->src_ctx, buffersrc,
|
|
"in", args, NULL, vf->graph);
|
|
if (ret < 0) filters_err("Cannot create video buffer source\n");
|
|
if (ictx->vc && ictx->vc->hw_frames_ctx) {
|
|
// XXX a bit problematic in that it's set before decoder is fully ready
|
|
AVBufferSrcParameters *srcpar = av_buffersrc_parameters_alloc();
|
|
srcpar->hw_frames_ctx = ictx->vc->hw_frames_ctx;
|
|
vf->hwframes = ictx->vc->hw_frames_ctx->data;
|
|
av_buffersrc_parameters_set(vf->src_ctx, srcpar);
|
|
av_freep(&srcpar);
|
|
}
|
|
|
|
/* buffer video sink: to terminate the filter chain. */
|
|
ret = avfilter_graph_create_filter(&vf->sink_ctx, buffersink,
|
|
"out", NULL, NULL, vf->graph);
|
|
if (ret < 0) filters_err("Cannot create video buffer sink\n");
|
|
|
|
ret = av_opt_set_int_list(vf->sink_ctx, "pix_fmts", pix_fmts,
|
|
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
|
|
if (ret < 0) filters_err("Cannot set output pixel format\n");
|
|
|
|
/*
|
|
* Set the endpoints for the filter graph. The filter_graph will
|
|
* be linked to the graph described by filters_descr.
|
|
*/
|
|
|
|
/*
|
|
* The buffer source output must be connected to the input pad of
|
|
* the first filter described by filters_descr; since the first
|
|
* filter input label is not specified, it is set to "in" by
|
|
* default.
|
|
*/
|
|
outputs->name = av_strdup("in");
|
|
outputs->filter_ctx = vf->src_ctx;
|
|
outputs->pad_idx = 0;
|
|
outputs->next = NULL;
|
|
|
|
/*
|
|
* The buffer sink input must be connected to the output pad of
|
|
* the last filter described by filters_descr; since the last
|
|
* filter output label is not specified, it is set to "out" by
|
|
* default.
|
|
*/
|
|
inputs->name = av_strdup("out");
|
|
inputs->filter_ctx = vf->sink_ctx;
|
|
inputs->pad_idx = 0;
|
|
inputs->next = NULL;
|
|
|
|
ret = avfilter_graph_parse_ptr(vf->graph, filters_descr,
|
|
&inputs, &outputs, NULL);
|
|
if (ret < 0) filters_err("Unable to parse video filters desc\n");
|
|
|
|
ret = avfilter_graph_config(vf->graph, NULL);
|
|
if (ret < 0) filters_err("Unable configure video filtergraph\n");
|
|
|
|
vf->frame = av_frame_alloc();
|
|
if (!vf->frame) filters_err("Unable to allocate video frame\n");
|
|
|
|
init_video_filters_cleanup:
|
|
avfilter_inout_free(&inputs);
|
|
avfilter_inout_free(&outputs);
|
|
|
|
vf->active = !ret;
|
|
return ret;
|
|
#undef filters_err
|
|
}
|
|
|
|
|
|
static int init_audio_filters(struct input_ctx *ictx, struct output_ctx *octx,
|
|
char *filters_descr)
|
|
{
|
|
#define af_err(msg) { \
|
|
if (!ret) ret = -1; \
|
|
fprintf(stderr, msg); \
|
|
goto init_audio_filters_cleanup; \
|
|
}
|
|
int ret = 0;
|
|
char args[512];
|
|
const AVFilter *buffersrc = avfilter_get_by_name("abuffer");
|
|
const AVFilter *buffersink = avfilter_get_by_name("abuffersink");
|
|
AVFilterInOut *outputs = avfilter_inout_alloc();
|
|
AVFilterInOut *inputs = avfilter_inout_alloc();
|
|
struct filter_ctx *af = &octx->af;
|
|
AVRational time_base = ictx->ic->streams[ictx->ai]->time_base;
|
|
|
|
|
|
af->graph = avfilter_graph_alloc();
|
|
|
|
if (!outputs || !inputs || !af->graph) {
|
|
ret = AVERROR(ENOMEM);
|
|
af_err("Unble to allocate audio filters\n");
|
|
}
|
|
|
|
/* buffer audio source: the decoded frames from the decoder will be inserted here. */
|
|
snprintf(args, sizeof args,
|
|
"sample_rate=%d:sample_fmt=%d:channel_layout=0x%"PRIx64":channels=%d:"
|
|
"time_base=%d/%d",
|
|
ictx->ac->sample_rate, ictx->ac->sample_fmt, ictx->ac->channel_layout,
|
|
ictx->ac->channels, time_base.num, time_base.den);
|
|
|
|
ret = avfilter_graph_create_filter(&af->src_ctx, buffersrc,
|
|
"in", args, NULL, af->graph);
|
|
if (ret < 0) af_err("Cannot create audio buffer source\n");
|
|
|
|
/* buffer audio sink: to terminate the filter chain. */
|
|
ret = avfilter_graph_create_filter(&af->sink_ctx, buffersink,
|
|
"out", NULL, NULL, af->graph);
|
|
if (ret < 0) af_err("Cannot create audio buffer sink\n");
|
|
|
|
/*
|
|
* Set the endpoints for the filter graph. The filter_graph will
|
|
* be linked to the graph described by filters_descr.
|
|
*/
|
|
|
|
/*
|
|
* The buffer source output must be connected to the input pad of
|
|
* the first filter described by filters_descr; since the first
|
|
* filter input label is not specified, it is set to "in" by
|
|
* default.
|
|
*/
|
|
outputs->name = av_strdup("in");
|
|
outputs->filter_ctx = af->src_ctx;
|
|
outputs->pad_idx = 0;
|
|
outputs->next = NULL;
|
|
|
|
/*
|
|
* The buffer sink input must be connected to the output pad of
|
|
* the last filter described by filters_descr; since the last
|
|
* filter output label is not specified, it is set to "out" by
|
|
* default.
|
|
*/
|
|
inputs->name = av_strdup("out");
|
|
inputs->filter_ctx = af->sink_ctx;
|
|
inputs->pad_idx = 0;
|
|
inputs->next = NULL;
|
|
|
|
ret = avfilter_graph_parse_ptr(af->graph, filters_descr,
|
|
&inputs, &outputs, NULL);
|
|
if (ret < 0) af_err("Unable to parse audio filters desc\n");
|
|
|
|
ret = avfilter_graph_config(af->graph, NULL);
|
|
if (ret < 0) af_err("Unable configure audio filtergraph\n");
|
|
|
|
af->frame = av_frame_alloc();
|
|
if (!af->frame) af_err("Unable to allocate audio frame\n");
|
|
|
|
init_audio_filters_cleanup:
|
|
avfilter_inout_free(&inputs);
|
|
avfilter_inout_free(&outputs);
|
|
|
|
af->active = !ret;
|
|
return ret;
|
|
#undef af_err
|
|
}
|
|
|
|
int process_in(struct input_ctx *ictx, AVFrame *frame, AVPacket *pkt)
|
|
{
|
|
#define dec_err(msg) { \
|
|
if (!ret) ret = -1; \
|
|
fprintf(stderr, msg); \
|
|
goto dec_cleanup; \
|
|
}
|
|
int ret = 0;
|
|
|
|
av_init_packet(pkt);
|
|
// loop until a new frame has been decoded, or EAGAIN
|
|
while (1) {
|
|
AVStream *ist = NULL;
|
|
AVCodecContext *decoder = NULL;
|
|
ret = av_read_frame(ictx->ic, pkt);
|
|
if (ret == AVERROR_EOF) goto dec_flush;
|
|
else if (ret < 0) dec_err("Unable to read input\n");
|
|
ist = ictx->ic->streams[pkt->stream_index];
|
|
if (ist->index == ictx->vi && ictx->vc) decoder = ictx->vc;
|
|
else if (ist->index == ictx->ai && ictx->ac) decoder = ictx->ac;
|
|
else dec_err("Could not find decoder for stream\n");
|
|
|
|
ret = avcodec_send_packet(decoder, pkt);
|
|
if (ret < 0) dec_err("Error sending packet to decoder\n");
|
|
ret = avcodec_receive_frame(decoder, frame);
|
|
if (ret == AVERROR(EAGAIN)) {
|
|
av_packet_unref(pkt);
|
|
continue;
|
|
}
|
|
else if (ret < 0) dec_err("Error receiving frame from decoder\n");
|
|
break;
|
|
}
|
|
|
|
dec_cleanup:
|
|
if (ret < 0) av_packet_unref(pkt); // XXX necessary? or have caller do it?
|
|
return ret;
|
|
|
|
dec_flush:
|
|
if (ictx->vc) {
|
|
avcodec_send_packet(ictx->vc, NULL);
|
|
ret = avcodec_receive_frame(ictx->vc, frame);
|
|
pkt->stream_index = ictx->vi; // XXX ugly?
|
|
if (!ret) return ret;
|
|
}
|
|
if (ictx->ac) {
|
|
avcodec_send_packet(ictx->ac, NULL);
|
|
ret = avcodec_receive_frame(ictx->ac, frame);
|
|
pkt->stream_index = ictx->ai; // XXX ugly?
|
|
}
|
|
return ret;
|
|
|
|
#undef dec_err
|
|
}
|
|
|
|
int mux(AVPacket *pkt, AVRational tb, struct output_ctx *octx, AVStream *ost)
|
|
{
|
|
pkt->stream_index = ost->index;
|
|
if (av_cmp_q(tb, ost->time_base)) {
|
|
av_packet_rescale_ts(pkt, tb, ost->time_base);
|
|
}
|
|
|
|
// drop any preroll audio. may need to drop multiple packets for multichannel
|
|
// XXX this breaks if preroll isn't exactly one AVPacket or drop_ts == 0
|
|
// hasn't been a problem in practice (so far)
|
|
if (AVMEDIA_TYPE_AUDIO == ost->codecpar->codec_type) {
|
|
if (octx->drop_ts == AV_NOPTS_VALUE) octx->drop_ts = pkt->pts;
|
|
if (pkt->pts && pkt->pts == octx->drop_ts) return 0;
|
|
}
|
|
|
|
return av_interleaved_write_frame(octx->oc, pkt);
|
|
}
|
|
|
|
int encode(AVCodecContext* encoder, AVFrame *frame, struct output_ctx* octx, AVStream* ost) {
|
|
#define encode_err(msg) { \
|
|
char errstr[AV_ERROR_MAX_STRING_SIZE] = {0}; \
|
|
if (!ret) { fprintf(stderr, "should not happen\n"); ret = AVERROR(ENOMEM); } \
|
|
if (ret < -1) av_strerror(ret, errstr, sizeof errstr); \
|
|
fprintf(stderr, "%s: %s", msg, errstr); \
|
|
goto encode_cleanup; \
|
|
}
|
|
|
|
AVPacket pkt = {0};
|
|
|
|
if (AVMEDIA_TYPE_VIDEO == ost->codecpar->codec_type && frame) {
|
|
octx->res->frames++;
|
|
octx->res->pixels += encoder->width * encoder->height;
|
|
}
|
|
|
|
int ret = avcodec_send_frame(encoder, frame);
|
|
if (AVERROR_EOF == ret) ; // continue ; drain encoder
|
|
else if (ret < 0) encode_err("Error sending frame to encoder");
|
|
|
|
while (1) {
|
|
av_init_packet(&pkt);
|
|
ret = avcodec_receive_packet(encoder, &pkt);
|
|
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) goto encode_cleanup;
|
|
if (ret < 0) encode_err("Error receiving packet from encoder\n");
|
|
ret = mux(&pkt, encoder->time_base, octx, ost);
|
|
if (ret < 0) goto encode_cleanup;
|
|
av_packet_unref(&pkt);
|
|
}
|
|
|
|
encode_cleanup:
|
|
av_packet_unref(&pkt);
|
|
return ret;
|
|
|
|
#undef encode_err
|
|
}
|
|
|
|
int process_out(struct input_ctx *ictx, struct output_ctx *octx, AVCodecContext *encoder, AVStream *ost,
|
|
struct filter_ctx *filter, AVFrame *inf)
|
|
{
|
|
#define proc_err(msg) { \
|
|
char errstr[AV_ERROR_MAX_STRING_SIZE] = {0}; \
|
|
if (!ret) { fprintf(stderr, "u done messed up\n"); ret = AVERROR(ENOMEM); } \
|
|
if (ret < -1) av_strerror(ret, errstr, sizeof errstr); \
|
|
fprintf(stderr, "%s: %s", msg, errstr); \
|
|
goto proc_cleanup; \
|
|
}
|
|
int ret = 0;
|
|
|
|
if (!encoder) proc_err("Trying to transmux; not supported")
|
|
|
|
if (!filter || !filter->active) {
|
|
// No filter in between decoder and encoder, so use input frame directly
|
|
ret = encode(encoder, inf, octx, ost);
|
|
if (ret < 0) return ret;
|
|
}
|
|
|
|
// Sometimes we have to reset the filter if the HW context is updated
|
|
// because we initially set the filter before the decoder is fully ready
|
|
// and the decoder may change HW params
|
|
if (AVMEDIA_TYPE_VIDEO == ost->codecpar->codec_type &&
|
|
inf && inf->hw_frames_ctx && filter->hwframes &&
|
|
inf->hw_frames_ctx->data != filter->hwframes) {
|
|
free_filter(&octx->vf); // XXX really should flush filter first
|
|
ret = init_video_filters(ictx, octx);
|
|
if (ret < 0) return lpms_ERR_FILTERS;
|
|
}
|
|
ret = av_buffersrc_write_frame(filter->src_ctx, inf);
|
|
if (ret < 0) proc_err("Error feeding the filtergraph\n");
|
|
|
|
while (1) {
|
|
// Drain the filter. Each input frame may have multiple output frames
|
|
AVFrame *frame = filter->frame;
|
|
av_frame_unref(frame);
|
|
ret = av_buffersink_get_frame(filter->sink_ctx, frame);
|
|
frame->pict_type = AV_PICTURE_TYPE_NONE;
|
|
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
|
|
// no frame returned from filtergraph
|
|
// proceed only if the input frame is a flush (inf == null)
|
|
if (inf) return ret;
|
|
frame = NULL;
|
|
} else if (ret < 0) proc_err("Error consuming the filtergraph\n");
|
|
ret = encode(encoder, frame, octx, ost);
|
|
av_frame_unref(frame);
|
|
if (frame == NULL) return ret;
|
|
}
|
|
|
|
proc_cleanup:
|
|
return ret;
|
|
#undef proc_err
|
|
}
|
|
|
|
#define MAX_OUTPUT_SIZE 10
|
|
|
|
int lpms_transcode(input_params *inp, output_params *params,
|
|
output_results *results, int nb_outputs, output_results *decoded_results)
|
|
{
|
|
#define main_err(msg) { \
|
|
if (!ret) ret = AVERROR(EINVAL); \
|
|
fprintf(stderr, msg); \
|
|
goto transcode_cleanup; \
|
|
}
|
|
int ret = 0, i = 0;
|
|
struct input_ctx ictx;
|
|
AVPacket ipkt;
|
|
struct output_ctx outputs[MAX_OUTPUT_SIZE];
|
|
AVFrame *dframe = NULL;
|
|
|
|
memset(&ictx, 0, sizeof ictx);
|
|
memset(outputs, 0, sizeof outputs);
|
|
|
|
if (!inp) main_err("transcoder: Missing input params\n")
|
|
if (nb_outputs > MAX_OUTPUT_SIZE) main_err("transcoder: Too many outputs\n");
|
|
|
|
// populate input context
|
|
ret = open_input(inp, &ictx);
|
|
if (ret < 0) main_err("transcoder: Unable to open input\n");
|
|
|
|
// populate output contexts
|
|
for (i = 0; i < nb_outputs; i++) {
|
|
struct output_ctx *octx = &outputs[i];
|
|
octx->fname = params[i].fname;
|
|
octx->width = params[i].w;
|
|
octx->height = params[i].h;
|
|
octx->vencoder = params[i].vencoder;
|
|
octx->vfilters = params[i].vfilters;
|
|
if (params[i].bitrate) octx->bitrate = params[i].bitrate;
|
|
if (params[i].fps.den) octx->fps = params[i].fps;
|
|
octx->res = &results[i];
|
|
if (ictx.vc) {
|
|
ret = init_video_filters(&ictx, octx);
|
|
if (ret < 0) main_err("Unable to open video filter");
|
|
}
|
|
if (ictx.ac) {
|
|
char filter_str[256];
|
|
//snprintf(filter_str, sizeof filter_str, "aformat=sample_fmts=s16:channel_layouts=stereo:sample_rates=44100,asetnsamples=n=1152,aresample");
|
|
snprintf(filter_str, sizeof filter_str, "aformat=sample_fmts=fltp:channel_layouts=stereo:sample_rates=44100"); // set sample format and rate based on encoder support
|
|
ret = init_audio_filters(&ictx, octx, filter_str);
|
|
if (ret < 0) main_err("Unable to open audio filter");
|
|
}
|
|
ret = open_output(octx, &ictx);
|
|
if (ret < 0) main_err("transcoder: Unable to open output\n");
|
|
}
|
|
|
|
av_init_packet(&ipkt);
|
|
dframe = av_frame_alloc();
|
|
if (!dframe) main_err("transcoder: Unable to allocate frame\n");
|
|
|
|
while (1) {
|
|
AVStream *ist = NULL;
|
|
av_frame_unref(dframe);
|
|
ret = process_in(&ictx, dframe, &ipkt);
|
|
if (ret == AVERROR_EOF) break;
|
|
// Bail out on streams that appear to be broken
|
|
else if (ret < 0) main_err("transcoder: Could not decode; stopping\n");
|
|
ist = ictx.ic->streams[ipkt.stream_index];
|
|
|
|
if (AVMEDIA_TYPE_VIDEO == ist->codecpar->codec_type) {
|
|
decoded_results->frames++;
|
|
decoded_results->pixels += dframe->width * dframe->height;
|
|
}
|
|
|
|
for (i = 0; i < nb_outputs; i++) {
|
|
struct output_ctx *octx = &outputs[i];
|
|
struct filter_ctx *filter = NULL;
|
|
AVStream *ost = NULL;
|
|
AVCodecContext *encoder = NULL;
|
|
|
|
if (ist->index == ictx.vi && ictx.vc) {
|
|
ost = octx->oc->streams[0];
|
|
encoder = octx->vc;
|
|
filter = &octx->vf;
|
|
} else if (ist->index == ictx.ai && ictx.ac) {
|
|
ost = octx->oc->streams[!!ictx.vc]; // depends on whether video exists
|
|
encoder = octx->ac;
|
|
filter = &octx->af;
|
|
} else main_err("transcoder: Got unknown stream\n"); // XXX could be legit; eg subs, secondary streams
|
|
|
|
ret = process_out(&ictx, octx, encoder, ost, filter, dframe);
|
|
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) continue;
|
|
else if (ret < 0) main_err("transcoder: Error encoding\n");
|
|
}
|
|
|
|
whileloop_end:
|
|
av_packet_unref(&ipkt);
|
|
}
|
|
|
|
// flush outputs
|
|
for (i = 0; i < nb_outputs; i++) {
|
|
struct output_ctx *octx = &outputs[i];
|
|
// only issue w this flushing method is it's not necessarily sequential
|
|
// wrt all the outputs; might want to iterate on each output per frame?
|
|
ret = 0;
|
|
if (octx->vc) { // flush video
|
|
while (!ret || ret == AVERROR(EAGAIN)) {
|
|
ret = process_out(&ictx, octx, octx->vc, octx->oc->streams[octx->vi], &octx->vf, NULL);
|
|
}
|
|
}
|
|
ret = 0;
|
|
if (octx->ac) { // flush audio
|
|
while (!ret || ret == AVERROR(EAGAIN)) {
|
|
ret = process_out(&ictx, octx, octx->ac, octx->oc->streams[octx->ai], &octx->af, NULL);
|
|
}
|
|
}
|
|
av_interleaved_write_frame(octx->oc, NULL); // flush muxer
|
|
ret = av_write_trailer(octx->oc);
|
|
if (ret < 0) main_err("transcoder: Unable to write trailer");
|
|
}
|
|
|
|
transcode_cleanup:
|
|
free_input(&ictx);
|
|
for (i = 0; i < MAX_OUTPUT_SIZE; i++) free_output(&outputs[i]);
|
|
if (dframe) av_frame_free(&dframe);
|
|
return ret == AVERROR_EOF ? 0 : ret;
|
|
#undef main_err
|
|
}
|