用ffmpeg4.1.4开发一个播放器,解码过程如下,在每个函数前设置标志,测试发现程序阻塞在avcodec_send_packet函数。
while(true){
av_read_frame
avcodec_send_packet
avcodec_receive_frame
av_packet_unref
}
解释如下:
avcodec_send_packet
和 avcodec_receive_frame
的工作原理是,avcodec_send_packet
向解码器发送压缩数据包,而 avcodec_receive_frame
从解码器接收解码后的帧。解码器内部有一个缓冲区,用于存储解码过程中间的数据。如果缓冲区已满(即没有足够的空间来存储新的数据包),avcodec_send_packet
就会阻塞,直到有足够的空间。
我用的是处理器是rk3288,后台打印信息如下:
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 1615 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 2107 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 2064 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 1504 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 2588 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 1642 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 1721 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Received a frame.
[h264_rkmpp @ 0xa5795e20] Wrote 1437 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2307 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1427 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1638 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2303 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1342 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2193 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1567 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2247 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1396 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2375 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1520 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1663 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2273 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1288 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 2340 bytes to decoder
[h264_rkmpp @ 0xa5795e20] Wrote 1399 bytes to decoder
那avcodec_send_packe
函数为什么会“get a frame”呢?源码如下,猜测是阻塞到这里
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
AVCodecInternal *avci = avctx->internal;
int ret;
if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
return AVERROR(EINVAL);
if (avctx->internal->draining)
return AVERROR_EOF;
if (avpkt && !avpkt->size && avpkt->data)
return AVERROR(EINVAL);
av_packet_unref(avci->buffer_pkt);
if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
ret = av_packet_ref(avci->buffer_pkt, avpkt);
if (ret < 0)
return ret;
}
ret = av_bsf_send_packet(avci->filter.bsfs[0], avci->buffer_pkt);
if (ret < 0) {
av_packet_unref(avci->buffer_pkt);
return ret;
}
if (!avci->buffer_frame->buf[0]) {
ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
}
return 0;
}
static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
int ret;
while (!frame->buf[0]) {
ret = decode_simple_internal(avctx, frame);
if (ret < 0)
return ret;
}
return 0;
}
解决方法:
1、在调用 avcodec_send_packet
之前,可以先调用 avcodec_receive_frame
,即使你不确定是否有帧可以接收。这有助于清理缓冲区。
2、在while循环中等待avcodec_receive_frame
ret = avcodec_send_packet(codec_ctx, packet);
while (ret >= 0) {
AVFrame *frame = av_frame_alloc();
if (!frame) {
// 处理内存分配错误
return AVERROR(ENOMEM);
}
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// 没有更多帧可以接收,退出循环
av_frame_free(&frame);
break;
} else if (ret < 0) {
// 处理接收帧错误
av_frame_free(&frame);
return ret;
}
}
3、正常情况下向解码器发送一包数据会收到一个解码后的帧,因此增加一个检测机制,当多次发送数据包但没有收到解码帧后则重置解码器。
void reset_decoder(AVCodecContext* codecContext) {
// 刷新解码器缓冲区
avcodec_flush_buffers(codecContext);
// 关闭解码器
avcodec_close(codecContext);
// 重新打开解码器
if (avcodec_open2(codecContext, codecContext->codec, nullptr) < 0) {
std::cerr << "Could not re-open codec" << std::endl;
}
}