How to encode decode by ffmpeg api - dalmatele/ffmpeg-libav-tutorial GitHub Wiki
Origina link: https://blogs.gentoo.org
New AVCodec API
Another week another API landed in the tree and since I spent some time drafting it, I guess I should describe how to use it now what is implemented. This is part I
What is here now
Between theory and practice there is a bit of discussion and obviously the (lack) of time to implement, so here what is different from what I drafted originally:
- Function Names: push got renamed to send and pull got renamed to receive.
- No separated function to probe the process state, need_data and have_data are not here.
- No codecs ported to use the new API, so no actual asyncronicity for now.
- Subtitles arenโt supported yet.
New API
There are just 4 new functions replacing both audio-specific and video-specific ones:
// Decode
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
// Encode
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
The workflow is sort of simple:
โ You setup the decoder or the encoder as usual
โ You feed data using the avcodec_send_* functions until you get a AVERROR(EAGAIN), that signals that the internal input buffer is full.
โ You get the data back using the matching avcodec_receive_* function until you get a AVERROR(EAGAIN), signalling that the internal output buffer is empty.
โ Once you are done feeding data you have to pass a NULL to signal the end of stream.
โ You can keep calling the avcodec_receive_* function until you get AVERROR_EOF.
โ You free the contexts as usual.
Decoding examples
Setup
The setup uses the usual avcodec_open2.
...
c = avcodec_alloc_context3(codec);
ret = avcodec_open2(c, codec, &opts);
if (ret < 0)
...
Simple decoding loop
People using the old API usually have some kind of simple loop like
while (get_packet(pkt)) {
ret = avcodec_decode_video2(c, picture, &got_picture, pkt);
if (ret < 0) {
...
}
if (got_picture) {
...
}
}
The old functions can be replaced by calling something like the following.
// The flush packet is a non-NULL packet with size 0 and data NULL
int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
{
int ret;
*got_frame = 0;
if (pkt) {
ret = avcodec_send_packet(avctx, pkt);
// In particular, we don't expect AVERROR(EAGAIN), because we read all
// decoded frames with avcodec_receive_frame() until done.
if (ret < 0)
return ret == AVERROR_EOF ? 0 : ret;
}
ret = avcodec_receive_frame(avctx, frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
if (ret >= 0)
*got_frame = 1;
return 0;
}
Callback approach
Since the new API will output multiple frames in certain situations would be better to process them as they are produced.
// return 0 on success, negative on error
typedef int (*process_frame_cb)(void *ctx, AVFrame *frame);
int decode(AVCodecContext *avctx, AVFrame *pkt,
process_frame_cb cb, void *priv)
{
AVFrame *frame = av_frame_alloc();
int ret;
ret = avcodec_send_packet(avctx, pkt);
// Again EAGAIN is not expected
if (ret < 0)
goto out;
while (!ret) {
ret = avcodec_receive_frame(avctx, frame);
if (!ret)
ret = cb(priv, frame);
}
out:
av_frame_free(&frame);
if (ret == AVERROR(EAGAIN))
return 0;
return ret;
}
Separated threads
The new API makes sort of easy to split the workload in two separated threads.
// Assume we have context with a mutex, a condition variable and the AVCodecContext
// Feeding loop
{
AVPacket *pkt = NULL;
while ((ret = get_packet(ctx, pkt)) >= 0) {
pthread_mutex_lock(&ctx->lock);
ret = avcodec_send_packet(avctx, pkt);
if (!ret) {
pthread_cond_signal(&ctx->cond);
} else if (ret == AVERROR(EAGAIN)) {
// Signal the draining loop
pthread_cond_signal(&ctx->cond);
// Wait here
pthread_cond_wait(&ctx->cond, &ctx->mutex);
} else if (ret < 0)
goto out;
pthread_mutex_unlock(&ctx->lock);
}
pthread_mutex_lock(&ctx->lock);
ret = avcodec_send_packet(avctx, NULL);
pthread_cond_signal(&ctx->cond);
out:
pthread_mutex_unlock(&ctx->lock)
return ret;
}
// Draining loop
{
AVFrame *frame = av_frame_alloc();
while (!done) {
pthread_mutex_lock(&ctx->lock);
ret = avcodec_receive_frame(avctx, frame);
if (!ret) {
pthread_cond_signal(&ctx->cond);
} else if (ret == AVERROR(EAGAIN)) {
// Signal the feeding loop
pthread_cond_signal(&ctx->cond);
// Wait
pthread_cond_wait(&ctx->cond, &ctx->mutex);
} else if (ret < 0)
goto out;
pthread_mutex_unlock(&ctx->lock);
if (!ret) {
do_something(frame);
}
}
out:
pthread_mutex_unlock(&ctx->lock)
return ret;
}
It isnโt as neat as having all this abstracted away, but is mostly workable.
Encoding Examples
Simple encoding loop
Some compatibility with the old API can be achieved using something along the lines of:
int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
{
int ret;
*got_packet = 0;
ret = avcodec_send_frame(avctx, frame);
if (ret < 0)
return ret;
ret = avcodec_receive_packet(avctx, pkt);
if (!ret)
*got_packet = 1;
if (ret == AVERROR(EAGAIN))
return 0;
return ret;
}
Callback approach
Since for each input multiple output could be produced, would be better to loop over the output as soon as possible.
// return 0 on success, negative on error
typedef int (*process_packet_cb)(void *ctx, AVPacket *pkt);
int encode(AVCodecContext *avctx, AVFrame *frame,
process_packet_cb cb, void *priv)
{
AVPacket *pkt = av_packet_alloc();
int ret;
ret = avcodec_send_frame(avctx, frame);
if (ret < 0)
goto out;
while (!ret) {
ret = avcodec_receive_packet(avctx, pkt);
if (!ret)
ret = cb(priv, pkt);
}
out:
av_packet_free(&pkt);
if (ret == AVERROR(EAGAIN))
return 0;
return ret;
}
The I/O should happen in a different thread when possible so the callback should just enqueue the packets.