用ffmpeg4.1.4開發一個播放器,解碼過程如下,在每個函數前設置標志,測試發現程序阻塞在avcodec_send_packet函數。
while(true){av_read_frameavcodec_send_packetavcodec_receive_frameav_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;}
}