Handling AVERROR(EAGAIN) and AVERROR_EOF in FFmpeg
In the FFmpeg C API, processing audio and video relies on an
asynchronous send/receive model where AVERROR(EAGAIN) and
AVERROR_EOF are operational flow-control signals rather
than critical errors. This guide explains the exact meaning of these
return codes within the avcodec_send_* and
avcodec_receive_* functions, detailing how to properly
structure your encoding or decoding loops to prevent deadlocks, data
loss, and segmentation faults.
The Send/Receive API Paradigm
FFmpeg decouples input and output using two primary function pairs:
- Decoding:
avcodec_send_packet()supplies compressed data, andavcodec_receive_frame()extracts uncompressed frames. - Encoding:
avcodec_send_frame()supplies raw frames, andavcodec_receive_packet()extracts compressed packets.
Because a single input packet can generate zero, one, or multiple
frames (and vice versa for encoding), the API relies on
AVERROR(EAGAIN) and AVERROR_EOF to manage
internal buffers.
What AVERROR(EAGAIN)
Means
AVERROR(EAGAIN) indicates that the internal state cannot
proceed with the requested operation until you perform the complementary
action.
- When returned by
avcodec_receive_frame()oravcodec_receive_packet():- Meaning: The internal buffer is empty. The codec requires more input before it can emit new output.
- Action: Stop calling the
receivefunction. Read the next packet or frame from your demuxer/source and provide it via the correspondingsendfunction.
- When returned by
avcodec_send_packet()oravcodec_send_frame():- Meaning: The internal buffer is full. The codec cannot accept new input until the existing output is consumed.
- Action: Stop sending data. Repeatedly call the
receivefunction until it returnsAVERROR(EAGAIN), draining the internal buffer, then retry sending the original input.
What AVERROR_EOF Means
AVERROR_EOF indicates that the stream has ended and the
codec has been completely flushed.
- Flushing the Codec:
- To signal the end of a stream, send a
NULLpointer toavcodec_send_packet()oravcodec_send_frame(). - Once the flush packet/frame is sent, calling the
sendfunction again will returnAVERROR_EOF.
- To signal the end of a stream, send a
- Draining Remaining Data:
- After flushing, existing buffered frames or packets remain inside the codec.
- Continue calling
avcodec_receive_frame()oravcodec_receive_packet()in a loop. It will return valid data until the codec is entirely empty. - Once all buffered data is output, the
receivefunction returnsAVERROR_EOF. At this point, no further output will be produced, and the processing loop should terminate.
Canonical Decoding Loop Example
The following pattern demonstrates how to handle both return codes correctly during decoding:
int ret = 0;
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_idx) {
// Send packet to decoder
ret = avcodec_send_packet(codec_ctx, pkt);
if (ret < 0) {
// AVERROR(EAGAIN) here means you must drain frames first,
// though standard usage typically drains after every send.
fprintf(stderr, "Error sending packet: %d\n", ret);
break;
}
// Receive all available frames
while (ret >= 0) {
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// EAGAIN: Need more packets to output another frame.
// EOF: End of stream reached (if flushed).
break;
} else if (ret < 0) {
// Actual fatal error
fprintf(stderr, "Error receiving frame: %d\n", ret);
goto end;
}
// Successfully decoded a frame
process_frame(frame);
av_frame_unref(frame);
}
}
av_packet_unref(pkt);
}
// Flush decoder: send NULL to signal EOF
ret = avcodec_send_packet(codec_ctx, NULL);
// Drain remaining cached frames
while (ret >= 0) {
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR_EOF) {
// Codec fully drained
break;
} else if (ret == AVERROR(EAGAIN)) {
// Should not occur after a flush, but break if it does
break;
} else if (ret < 0) {
fprintf(stderr, "Error draining frame: %d\n", ret);
break;
}
process_frame(frame);
av_frame_unref(frame);
}
end:
av_packet_free(&pkt);
av_frame_free(&frame);Key Rules to Remember
- Never treat
AVERROR(EAGAIN)as fatal: Treat it as a prompt to switch between reading and writing. - Do not discard unconsumed inputs: If
avcodec_send_*returnsAVERROR(EAGAIN), retain the current packet or frame, drain the output withavcodec_receive_*, and resubmit the same input. - Always drain after sending
NULL: Codecs often hold several delayed frames for B-frame reordering; skipping the finalAVERROR_EOFdrain loop will truncate the end of the media stream.