mirror of
https://github.com/owntone/owntone-server.git
synced 2025-03-31 17:53:49 -04:00
[transcode] Implement new ffmpeg encoding methods: avcodec_send_frame/avcodec_receive_packet
This commit is contained in:
parent
7c8eba74bb
commit
5afed60a42
229
src/transcode.c
229
src/transcode.c
@ -93,6 +93,10 @@ struct stream_ctx
|
|||||||
AVFilterContext *buffersink_ctx;
|
AVFilterContext *buffersink_ctx;
|
||||||
AVFilterContext *buffersrc_ctx;
|
AVFilterContext *buffersrc_ctx;
|
||||||
AVFilterGraph *filter_graph;
|
AVFilterGraph *filter_graph;
|
||||||
|
|
||||||
|
// Used for seeking
|
||||||
|
int64_t prev_pts;
|
||||||
|
int64_t offset_pts;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct decode_ctx
|
struct decode_ctx
|
||||||
@ -139,9 +143,11 @@ struct encode_ctx
|
|||||||
// The ffmpeg muxer writes to this buffer using the avio_evbuffer interface
|
// The ffmpeg muxer writes to this buffer using the avio_evbuffer interface
|
||||||
struct evbuffer *obuf;
|
struct evbuffer *obuf;
|
||||||
|
|
||||||
// Used for seeking
|
// Contains the most recent packet from av_buffersink_get_frame()
|
||||||
int64_t prev_pts[MAX_STREAMS];
|
AVFrame *filt_frame;
|
||||||
int64_t offset_pts[MAX_STREAMS];
|
|
||||||
|
// Contains the most recent packet from avcodec_receive_packet()
|
||||||
|
AVPacket *encoded_pkt;
|
||||||
|
|
||||||
// How many output bytes we have processed in total
|
// How many output bytes we have processed in total
|
||||||
off_t total_bytes;
|
off_t total_bytes;
|
||||||
@ -457,69 +463,67 @@ read_packet(AVPacket *packet, enum AVMediaType *type, struct decode_ctx *ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
// Prepares a packet from the encoder for muxing
|
||||||
encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame, int *got_frame)
|
static void
|
||||||
|
packet_prepare(AVPacket *pkt, struct stream_ctx *s)
|
||||||
|
{
|
||||||
|
pkt->stream_index = s->stream->index;
|
||||||
|
|
||||||
|
// This "wonderful" peace of code makes sure that the timestamp always increases,
|
||||||
|
// even if the user seeked backwards. The muxer will not accept non-increasing
|
||||||
|
// timestamps.
|
||||||
|
pkt->pts += s->offset_pts;
|
||||||
|
if (pkt->pts < s->prev_pts)
|
||||||
|
{
|
||||||
|
s->offset_pts += s->prev_pts - pkt->pts;
|
||||||
|
pkt->pts = s->prev_pts;
|
||||||
|
}
|
||||||
|
s->prev_pts = pkt->pts;
|
||||||
|
pkt->dts = pkt->pts; //FIXME
|
||||||
|
|
||||||
|
av_packet_rescale_ts(pkt, s->codec->time_base, s->stream->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame)
|
||||||
{
|
{
|
||||||
AVPacket enc_pkt;
|
|
||||||
unsigned int stream_index;
|
|
||||||
int ret;
|
int ret;
|
||||||
int got_frame_local;
|
|
||||||
|
|
||||||
if (!got_frame)
|
|
||||||
got_frame = &got_frame_local;
|
|
||||||
|
|
||||||
stream_index = s->stream->index;
|
|
||||||
|
|
||||||
// Encode filtered frame
|
|
||||||
enc_pkt.data = NULL;
|
|
||||||
enc_pkt.size = 0;
|
|
||||||
av_init_packet(&enc_pkt);
|
|
||||||
|
|
||||||
if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
||||||
ret = avcodec_encode_audio2(s->codec, &enc_pkt, filt_frame, got_frame);
|
|
||||||
else if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
|
||||||
ret = avcodec_encode_video2(s->codec, &enc_pkt, filt_frame, got_frame);
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
|
// If filt_frame is null then flushing will be initiated by the codec
|
||||||
|
ret = avcodec_send_frame(s->codec, filt_frame);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return ret;
|
||||||
if (!(*got_frame))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Prepare packet for muxing
|
while (1)
|
||||||
enc_pkt.stream_index = stream_index;
|
|
||||||
|
|
||||||
// This "wonderful" peace of code makes sure that the timestamp never decreases,
|
|
||||||
// even if the user seeked backwards. The muxer will not accept decreasing
|
|
||||||
// timestamps
|
|
||||||
enc_pkt.pts += ctx->offset_pts[stream_index];
|
|
||||||
if (enc_pkt.pts < ctx->prev_pts[stream_index])
|
|
||||||
{
|
{
|
||||||
ctx->offset_pts[stream_index] += ctx->prev_pts[stream_index] - enc_pkt.pts;
|
ret = avcodec_receive_packet(s->codec, ctx->encoded_pkt);
|
||||||
enc_pkt.pts = ctx->prev_pts[stream_index];
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
if (ret == AVERROR(EAGAIN))
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet_prepare(ctx->encoded_pkt, s);
|
||||||
|
|
||||||
|
ret = av_interleaved_write_frame(ctx->ofmt_ctx, ctx->encoded_pkt);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
ctx->prev_pts[stream_index] = enc_pkt.pts;
|
|
||||||
enc_pkt.dts = enc_pkt.pts; //FIXME
|
|
||||||
|
|
||||||
av_packet_rescale_ts(&enc_pkt, s->codec->time_base, s->stream->time_base);
|
|
||||||
|
|
||||||
// Mux encoded frame
|
|
||||||
ret = av_interleaved_write_frame(ctx->ofmt_ctx, &enc_pkt);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAVE_DECL_AV_BUFFERSRC_ADD_FRAME_FLAGS && HAVE_DECL_AV_BUFFERSINK_GET_FRAME
|
|
||||||
static int
|
static int
|
||||||
filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame)
|
filter_encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame)
|
||||||
{
|
{
|
||||||
AVFrame *filt_frame;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
// Push the decoded frame into the filtergraph
|
// Push the decoded frame into the filtergraph
|
||||||
if (frame)
|
if (frame)
|
||||||
{
|
{
|
||||||
ret = av_buffersrc_add_frame_flags(s->buffersrc_ctx, frame, 0);
|
ret = av_buffersrc_add_frame(s->buffersrc_ctx, frame);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret));
|
DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret));
|
||||||
@ -527,95 +531,28 @@ filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull filtered frames from the filtergraph
|
// Pull filtered frames from the filtergraph and pass to encoder
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
filt_frame = av_frame_alloc();
|
ret = av_buffersink_get_frame(s->buffersink_ctx, ctx->filt_frame);
|
||||||
if (!filt_frame)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = av_buffersink_get_frame(s->buffersink_ctx, filt_frame);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
/* if no more frames for output - returns AVERROR(EAGAIN)
|
if (!frame) // We are flushing
|
||||||
* if flushed and no more frames for output - returns AVERROR_EOF
|
ret = encode_write(ctx, s, NULL);
|
||||||
* rewrite retcode to 0 to show it as normal procedure completion
|
else if (ret == AVERROR(EAGAIN))
|
||||||
*/
|
|
||||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
av_frame_free(&filt_frame);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
filt_frame->pict_type = AV_PICTURE_TYPE_NONE;
|
ret = encode_write(ctx, s, ctx->filt_frame);
|
||||||
ret = encode_write_frame(ctx, s, filt_frame, NULL);
|
av_frame_unref(ctx->filt_frame);
|
||||||
av_frame_free(&filt_frame);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
static int
|
|
||||||
filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame)
|
|
||||||
{
|
|
||||||
AVFilterBufferRef *picref;
|
|
||||||
AVFrame *filt_frame;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
// Push the decoded frame into the filtergraph
|
|
||||||
if (frame)
|
|
||||||
{
|
|
||||||
ret = av_buffersrc_write_frame(s->buffersrc_ctx, frame);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull filtered frames from the filtergraph
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
filt_frame = av_frame_alloc();
|
|
||||||
if (!filt_frame)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO && !(s->codec->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE))
|
|
||||||
ret = av_buffersink_read_samples(s->buffersink_ctx, &picref, s->codec->frame_size);
|
|
||||||
else
|
|
||||||
ret = av_buffersink_read(s->buffersink_ctx, &picref);
|
|
||||||
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
/* if no more frames for output - returns AVERROR(EAGAIN)
|
|
||||||
* if flushed and no more frames for output - returns AVERROR_EOF
|
|
||||||
* rewrite retcode to 0 to show it as normal procedure completion
|
|
||||||
*/
|
|
||||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
||||||
ret = 0;
|
|
||||||
av_frame_free(&filt_frame);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
avfilter_copy_buf_props(filt_frame, picref);
|
|
||||||
ret = encode_write_frame(ctx, s, filt_frame, NULL);
|
|
||||||
av_frame_free(&filt_frame);
|
|
||||||
avfilter_unref_buffer(picref);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Will step through each stream and feed the stream decoder with empty packets
|
/* Will step through each stream and feed the stream decoder with empty packets
|
||||||
* to see if the decoder has more frames lined up. Will return non-zero if a
|
* to see if the decoder has more frames lined up. Will return non-zero if a
|
||||||
@ -647,24 +584,6 @@ flush_decoder(AVFrame *frame, enum AVMediaType *type, struct decode_ctx *ctx)
|
|||||||
return got_frame;
|
return got_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
flush_encoder(struct encode_ctx *ctx, struct stream_ctx *s)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
int got_frame;
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", s->stream->index);
|
|
||||||
|
|
||||||
if (!(s->codec->codec->capabilities & CODEC_CAP_DELAY))
|
|
||||||
return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
ret = encode_write_frame(ctx, s, NULL, &got_frame);
|
|
||||||
}
|
|
||||||
while ((ret == 0) && got_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --------------------------- INPUT/OUTPUT INIT --------------------------- */
|
/* --------------------------- INPUT/OUTPUT INIT --------------------------- */
|
||||||
|
|
||||||
@ -1069,6 +988,8 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind,
|
|||||||
|
|
||||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx)));
|
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx)));
|
||||||
|
|
||||||
|
av_init_packet(&ctx->packet);
|
||||||
|
|
||||||
ctx->duration = song_length;
|
ctx->duration = song_length;
|
||||||
ctx->data_kind = data_kind;
|
ctx->data_kind = data_kind;
|
||||||
|
|
||||||
@ -1078,8 +999,6 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_init_packet(&ctx->packet);
|
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,6 +1008,8 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct
|
|||||||
struct encode_ctx *ctx;
|
struct encode_ctx *ctx;
|
||||||
|
|
||||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx)));
|
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx)));
|
||||||
|
CHECK_NULL(L_XCODE, ctx->filt_frame = av_frame_alloc());
|
||||||
|
CHECK_NULL(L_XCODE, ctx->encoded_pkt = av_packet_alloc());
|
||||||
|
|
||||||
if ((init_settings(&ctx->settings, profile) < 0) || (open_output(ctx, src_ctx) < 0))
|
if ((init_settings(&ctx->settings, profile) < 0) || (open_output(ctx, src_ctx) < 0))
|
||||||
{
|
{
|
||||||
@ -1281,24 +1202,24 @@ transcode_decode_cleanup(struct decode_ctx *ctx)
|
|||||||
void
|
void
|
||||||
transcode_encode_cleanup(struct encode_ctx *ctx)
|
transcode_encode_cleanup(struct encode_ctx *ctx)
|
||||||
{
|
{
|
||||||
|
// Flush audio encoder
|
||||||
if (ctx->audio_stream.stream)
|
if (ctx->audio_stream.stream)
|
||||||
{
|
filter_encode_write(ctx, &ctx->audio_stream, NULL);
|
||||||
if (ctx->audio_stream.filter_graph)
|
|
||||||
filter_encode_write_frame(ctx, &ctx->audio_stream, NULL);
|
|
||||||
flush_encoder(ctx, &ctx->audio_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Flush video encoder
|
||||||
if (ctx->video_stream.stream)
|
if (ctx->video_stream.stream)
|
||||||
{
|
filter_encode_write(ctx, &ctx->video_stream, NULL);
|
||||||
if (ctx->video_stream.filter_graph)
|
|
||||||
filter_encode_write_frame(ctx, &ctx->video_stream, NULL);
|
// Flush muxer
|
||||||
flush_encoder(ctx, &ctx->video_stream);
|
av_interleaved_write_frame(ctx->ofmt_ctx, NULL);
|
||||||
}
|
|
||||||
|
|
||||||
av_write_trailer(ctx->ofmt_ctx);
|
av_write_trailer(ctx->ofmt_ctx);
|
||||||
|
|
||||||
close_filters(ctx);
|
close_filters(ctx);
|
||||||
close_output(ctx);
|
close_output(ctx);
|
||||||
|
|
||||||
|
av_packet_free(&ctx->encoded_pkt);
|
||||||
|
av_frame_free(&ctx->filt_frame);
|
||||||
free(ctx);
|
free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1443,7 +1364,7 @@ transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct e
|
|||||||
ctx->settings.wavheader = 0;
|
ctx->settings.wavheader = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = filter_encode_write_frame(ctx, s, decoded->frame);
|
ret = filter_encode_write(ctx, s, decoded->frame);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret));
|
DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user