avcodec_decode_video解码的帧为什么后面的比前面的pts小

本文探讨了音视频编解码过程中的关键概念,如B帧处理、PTS和DTS的作用,以及如何确保音视频同步。通过具体示例,解析了不同场景下编解码器的工作机制。

如下代码:
while( av_read_frame(pFormatCtxSource,&packet)>=0 )
{
    if( packet.stream_index==videoStream )
    {
        int out_size = avcodec_decode_video(pCodecCtxSource,pFrameSource, &bFrameFinished, packet.data, packet.size); // Decode fromsource frame

        if( bFrameFinished )
        {
            pFrameSource->pts =av_rescale_q(packet.pts, pCodecCtxSource->time_base,pStCodec->time_base);
            int out_size =avcodec_encode_video(pStCodec, video_buffer, 200000, pFrameSource); // Encodeto output
            if( out_size>0 )
            {
                // ...
            }
        }
    }

    av_free_packet(&packet);

}

在我Decode的时候,第一帧得到的 pFrameSource->pts 96,再解第二帧的时候,pFrameSource->pts 计算完后就成了80几,后几帧也是比96小,过一会又会解出来一个100多的,接下来又是比100多小的,这是为什么?在Encode的时候,先 Encode一个pts=96的,再去Encode96小的帧就返回-1了,直到找到一个比96大的。

另外,我计算pts的方法正确吗?

 

答复:

Because you have B - Frame

for example:

the Inputsequence for video encoder
1  2  3   4   5    6   7
I   B   B   P  B   B   I

Let's take1,2,3.. as PTS for simplification

the out sequencefor video encoder ( this equals the decoder sequence)
1  4  2    3   7   5   6
I  P    B   B   I    B   B

you will get aPTS sequence as following:

1  4  2  3  7  5  6 

7  5 6sequence will be same as your question

 

问:

哦,那是不是我的pts不能这么算呢?而是要每次+1,对吗?那么,packet中的ptsdts要用在什么地方呢?我这样按存储顺序进行解码的话,显示之前是不是要自己进行缓存呢?谢谢!

 

另外,还有个问题,既然解码的时候,不一定是按照pts递增的顺序得到的解码后的画面,那我在编码图像的时候,是应该按照解码出来的帧顺序进行编码吗?还是把帧先缓存起来,最后严格接照图像的显示顺序来编码呢?用代码来表示,就是:
方法一:
while(av_read_frame )
{    
    解码;   
   pts+1;    
    编码;   
    输出;
}

方法二:
while(av_read_frame )
{
    解码;
   if( pts<previous )
    {
       缓存;
    }
   else
    {
       编码缓存的帧并写入文件;
    }
}

这两个方法,哪个是正确的呢?因为我看到网上的代码都用的是方法一,但是我觉得方法二是对的呀?

 

答:

the output of decoderis the right order for display because I/P frames will be cacheduntil next I/P

 

 

理解:

 

Decoder outputpts 是按正常的顺序,即显示的顺序输出的,如果有B帧,decoder会缓存。

encoder后,输出的是按dts输出的。

 

 

Pts,dts并不是时间戳,而更应该理解为frame的顺序序列号。由于每帧frame的帧率并不一定是一致的,可能会变化的。转换为时间戳的话,应该是(pts*帧率)。为加深理解

可以将pts比做是第ptsframe,假设每帧的帧率不变的话,则显示的时间戳为(pts*帧率),如果考虑帧率变化的,则要想办法将(pts*当前的帧率)累加到后面。

 

tutorial5中在decode 下增加trace后打印情况:

len1 = avcodec_decode_video(is->video_st->codec,pFrame, &frameFinished,

                      packet->data,packet->size);

      printf("-----------------------------------------------------------------------------n");

      printf("avcodec_decode_videopacket->pts:%x,packet->dts:%xn",packet->pts,packet->dts);

      printf("avcodec_decode_videopFrame->pkt_pts:%x,pFrame->pkt_dts:%x,pFrame->pts:%xn",pFrame->pkt_pts,pFrame->pkt_dts,pFrame->pts);

      if(pFrame->opaque)

           printf("avcodec_decode_video*(uint64_t *)pFrame->opaque:%xn",*(uint64_t *)pFrame->opaque);

 

其中播一个mp4文件的打印情况:

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:1ae,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1ae

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:1af,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1af

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24c,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1ac

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24d,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:24d

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24e,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video*(uint64_t *)pFrame->opaque:24e

 

以下为播放rm文件的情况:

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:1831b,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1831b

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18704,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18704

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18aed,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18aed

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18ed6,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18ed6

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:192bf,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:192bf

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:196a8,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:196a8

 

 

可以看出有的pts+1 累加,有的是加了很多,但都是按顺序累加的。当传人decoder前的packetpts时,则decoder后获取的frame将会赋值packet pts;当传人的packet 只是一帧的部分数据或是B帧,由于decoder出来的frame要按正常的pts顺序输出,有可能decoder不会获取到frame ,或decoder内部会缓存也不会输出frame,即framepts会为空。Frame pts(即opaque 为空的话则会看frame->dts,dts都没有的话才认为frame->pts0.

 

对于:

pts *= av_q2d(is->video_st->time_base);////pts*帧率

 

    // Did we get avideo frame?

   if(frameFinished) {

      pts =synchronize_video(is, pFrame, pts);

///// synchronize_video考虑了3中情况:

1.    pts拿到的话就用该pts

2.    pts没有拿到的话就用前一帧的pts时间

3.    如果该帧要重复显示,则将显示的数量*帧率,再加到前面的pts中。

     if(queue_picture(is, pFrame, pts) < 0) {/////传人decoder后的帧队列中,以便后续去获取show

 

 

static double synchronize_video(VideoState *is, AVFrame*src_frame, double pts) {

 

  doubleframe_delay;

 

  if(pts != 0) {

   

    is->video_clock = pts;

  } else {

   

    pts =is->video_clock;

  }

 

/////很关键:前面传进来的pts已经是时间戳了,是当前frame开始播放的时间戳,

/////下面frame_delay是该帧显示完将要花费的时间,(pts+frame_delay)也即是/////预测的下一帧将要播放的时间戳。

  frame_delay =av_q2d(is->video_st->codec->time_base);

 

 

//////重复多帧的话要累加上

  frame_delay +=src_frame->repeat_pict * (frame_delay * 0.5);

 is->video_clock += frame_delay;

  return pts;/////此时返回的值即为下一帧将要开始显示的时间戳。

}

 

 

 

///////开定时器去显示帧队列中的已经decode过的数据,按前面的分析我们已经知道帧队列中的数据已经是按pts顺序插入到队列中的。 Timer的作用就是有帧率不一致及重复帧的情况造成时间戳不是线性的,有快有慢,从而tutorial5才有timer的方式来播放:追赶

以下是一个网友很直观浅显的例子解释:

ccq(183892517) 17:05:21 if(packet->dts ==AV_NOPTS_VALUE 是不是就是没有获取到dts的情况?

David Cen(3727567) 17:06:44 就是有一把尺子 一只蚂蚁跟着一个标杆走  David Cen(3727567) 17:06:58 标杆是匀速的 蚂蚁或快或慢 DavidCen(3727567) 17:07:18 慢了你就抽它 让他跑起来 快了就拽它  David Cen(3727567) 17:07:38 这样音(标杆)视频(蚂蚁)就能同步了 DavidCen(3727567) 17:08:00 这里最大的问题就是音频是匀速的 视频是非线性的

 

另外:此时vp–>pts获取到的pts已经转化为时间戳了,这个时间戳为就是当前帧显示结束的时间戳,也即是下一帧将显示的预测时间戳。

static void video_refresh_timer(void *userdata) {

 

  VideoState *is = (VideoState*)userdata;

  VideoPicture *vp;

  double actual_delay, delay,sync_threshold, ref_clock, diff;

 

  if(is->video_st) {

    if(is->pictq_size == 0) {

      schedule_refresh(is, 1);

    } else {

      vp =&is->pictq[is->pictq_rindex];

 

      delay = vp->pts -is->frame_last_pts;   ////这是当前要显示的frame和下一                                                         //////将要显示的 frame的间隔时间

      if(delay <= 0 || delay>= 1.0) {

        

         delay =is->frame_last_delay;

      }

     

      is->frame_last_delay =delay;

      is->frame_last_pts =vp->pts;

 

     

      ref_clock = get_audio_clock(is);/////获取到声音当前播放的时间戳。

      diff = vp->pts -ref_clock;////// vp->pts实际上是预测的下一帧将要播放的开始时间,

 

//////////也就是说在diff这段时间中声音是匀速发生的,但是在delay这段时间frame的显示可能就会有快//////////慢的区别。

        

 

          

 

     

      sync_threshold = (delay >AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;

        

      if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

         if(diff <=-sync_threshold) {

           delay = 0;//////下一帧画面显示的时间和当前的声音很近的话加快显示下一帧(即后面video_display显示完当前帧后开启定时器很快去显示下一帧)

         } else if(diff >=sync_threshold) {

           delay = 2 * delay;//////下一帧开始显示的时间和当前声音的时间隔的比较长则延缓,即两帧画面间话的显示的时间长度大于两帧画面间的声音播放的时间,则我 们将两帧画显示的时候加倍拖长点,比如帧1和帧2的时间显示间隔为40ms,但帧1和帧2的声音播放时间为55ms,怎么办呢?我们不可能去打乱声音的质 量的,则我们采用的方法是:将两帧画面的播放间隔加大,本来是过30ms就要开始播下一帧的,我们改成60ms后才播下一帧。

         }

      }/////

////当然如果diff大于AV_NOSYNC_THRESHOLD,即快进的模式了,画面跳动太大,不存在音视频同步的问题了。

 

      is->frame_timer += delay;

     

      actual_delay =is->frame_timer - (av_gettime() / 1000000.0);

      if(actual_delay < 0.010){

        

         actual_delay = 0.010;

      }

      schedule_refresh(is,(int)(actual_delay * 1000 + 0.5));////开定时器去显示下一帧

     

      video_display(is);////立马显示当前帧

     

     

      if(++is->pictq_rindex ==VIDEO_PICTURE_QUEUE_SIZE) {

         is->pictq_rindex = 0;

      }

     SDL_LockMutex(is->pictq_mutex);

      is->pictq_size--;

     SDL_CondSignal(is->pictq_cond);

     SDL_UnlockMutex(is->pictq_mutex);

    }

  } else {

    schedule_refresh(is, 100);

  }

void *decode_thread(void *arg) { AVCodecContext *decoder = init_sw_decoder(); if (!decoder) { fprintf(stderr, "Failed to initialize software decoder\n"); return NULL; } while (1) { VideoFrame frame = frame_queue_pop(&g_decode_queue); // 解码 AVPacket pkt; av_init_packet(&pkt); pkt.data = frame.data; pkt.size = frame.size; if (avcodec_send_packet(decoder, &pkt) == 0) { AVFrame *av_frame = av_frame_alloc(); if (avcodec_receive_frame(decoder, av_frame) == 0) { // 创建显示 if(av_frame->width!=video_width || av_frame->height!=video_height){ video_width = av_frame->width; video_height = av_frame->height; if (g_rgb_buf) free(g_rgb_buf); if (g_sws_ctx) sws_freeContext(g_sws_ctx); g_rgb_buf = (uint8_t *)malloc(video_width * video_height * 4); g_sws_ctx = sws_getContext( video_width, video_height, av_frame->format, video_width, video_height, AV_PIX_FMT_BGRA, SWS_BILINEAR, NULL, NULL, NULL ); } VideoFrame display_frame; display_frame.data = malloc(BUF_SIZE); display_frame.size = BUF_SIZE; display_frame.pts = frame.pts; uint8_t *dst[1] = { g_rgb_buf }; int dst_linesize[1] = {video_width * 4}; sws_scale(g_sws_ctx, (const uint8_t * const *)av_frame->data, av_frame->linesize, 0, video_height, dst, dst_linesize); memcpy(display_frame.data, av_frame, BUF_SIZE); // YUV数据 // 推入显示队列 if (frame_queue_push(&g_display_queue, display_frame) != 0) { free(display_frame.data); // 队列满时丢弃 } } av_frame_free(&av_frame); } av_packet_free(&pkt); } avcodec_free_context(&decoder); return NULL; } 报错了
最新发布
08-02
<think>我们正在讨论的是解码线程中的错误,具体是关于`void decode_thread`函数中的错误,涉及到`AVCodecContext`和FFmpeg解码线程。 从引用中我们可以了解到一些FFmpeg解码相关的知识: 引用[1]提到了解码函数`avcodec_decode_video2`以及新的API `avcodec_send_packet`和`avcodec_receive_frame`,并展示了一个内部函数`decode_receive_frame_internal`。 引用[2]展示了一个解码视频的例子,包括如何找到解码器、初始化解析器、分配上下文、打开解码器以及分配。 引用[3]提到了FLV解码器的注册,其中解码函数是`ff_h263_decode_frame`。 引用[4]则提到了一个单独解码视频的例子。 用户的问题是关于解码线程中的错误,但并没有给出具体的错误信息。因此,我们需要先分析常见的解码线程错误,并给出一般性的解决步骤。 常见的解码线程错误可能包括: 1. 线程安全问题:FFmpeg的某些函数或结构体是线程安全的,如果在多个线程中使用同一个AVCodecContext而没有加锁,可能会导致问题。 2. 资源未正确初始化和释放:例如,没有正确初始化解码器,或者在退出线程时没有释放资源。 3. 数据包和的生命周期管理:在解码线程中,如果没有正确管理AVPacket和AVFrame的分配和释放,可能会导致内存泄漏或访问无效内存。 4. API使用当:例如,没有正确处理API的返回值,或者没有按照API的要求调用(比如在draining阶段需要发送空包等)。 由于用户提到了`decode_thread`函数,我们可以假设用户有一个类似以下结构的线程函数: ```c void decode_thread(void *arg) { // 初始化解码器 // 循环读取数据包并解码 while (1) { // 获取一个AVPacket // 发送数据包到解码器 // 接收解码后的 // 处理 } // 冲洗解码器(发送空包,接收剩余) // 释放资源 } ``` 下面,我们将详细分析可能出现的错误以及解决方法。 ### 常见错误及解决方法 1. **线程安全问题**: - 问题:多个线程同时使用同一个AVCodecContext进行解码操作。 - 解决:确保每个解码线程使用独立的AVCodecContext,或者使用互斥锁保护AVCodecContext。 2. **解码器初始化和关闭**: - 问题:解码器没有正确初始化或关闭。 - 解决:按照引用[2]中的步骤初始化: ```c AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_XXX); AVCodecParserContext *parser = av_parser_init(codec->id); AVCodecContext *ctx = avcodec_alloc_context3(codec); if (avcodec_open2(ctx, codec, NULL) < 0) { // 错误处理 } ``` 线程退出时,需要关闭和释放资源: ```c avcodec_close(ctx); avcodec_free_context(&ctx); av_parser_close(parser); ``` 3. **数据包和的管理**: - 问题:在发送数据包后,没有释放数据包;或者在接收后,没有正确释放。 - 解决:每次发送一个数据包后,如果再需要该数据包,应该调用`av_packet_unref`释放。对于,每次使用完一个后,应该调用`av_frame_unref`,最后在退出时释放通过`av_frame_alloc`分配的。 4. **API使用顺序**: - 问题:没有按照正确的顺序调用API。新的解码API要求先发送数据包,然后接收,直到返回`AVERROR(EAGAIN)`才发送下一个数据包。同时,在结束时需要发送一个空包(NULL)来冲洗解码器。 - 解决:参考以下代码结构: ```c AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); while (1) { // 读取一个数据包 // 如果读取结束,则发送一个空包来冲洗 int ret = avcodec_send_packet(ctx, pkt); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { // 错误处理 break; } // 如果发送的是非空包,可以在这里释放pkt,因为解码器内部已经复制了数据 if (pkt->data) { av_packet_unref(pkt); } while (ret >= 0) { ret = avcodec_receive_frame(ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { // 错误处理 break; } // 处理frame,比如显示或保存 // 处理完后,取消引用 av_frame_unref(frame); } // 如果已经发送了空包(冲洗)且收到了EOF,则退出 if (ret == AVERROR_EOF) { break; } } // 释放资源 av_packet_free(&pkt); av_frame_free(&frame); ``` 5. **处理返回值**: - 问题:忽略API的返回值,导致错误没有被发现。 - 解决:每次调用`avcodec_send_packet`和`avcodec_receive_frame`后,都应该检查返回值,并按照FFmpeg的文档处理错误。 6. **冲洗解码器**: - 问题:在数据包发送完毕后,没有发送空包来冲洗解码器,导致解码器中缓存的没有被输出。 - 解决:在读取完所有数据包后,发送一个空包(data=NULL, size=0)到解码器,然后继续接收直到返回`AVERROR_EOF`。 7. **多线程同步**: - 问题:主线程和解码线程之间的同步问题,例如主线程关闭解码器时,解码线程还在使用。 - 解决:使用条件变量或信号量来同步线程状态,确保解码线程完全退出后再释放资源。 8. **硬件加速**: - 问题:如果使用硬件加速,可能需要额外的设置和资源释放。 - 解决:参考FFmpeg关于硬件加速的文档,确保正确设置和清理。 由于用户没有提供具体的错误信息,以上是常见错误的总结。如果用户能提供具体的错误现象(如崩溃、解码等)或错误代码,可以进一步分析。 ### 示例代码结构 下面是一个更完整的解码线程函数示例(伪代码): ```c void *decode_thread(void *arg) { // 获取参数,如解码器上下文、输入队列等 MyContext *my_ctx = (MyContext*)arg; AVCodecContext *ctx = my_ctx->codec_ctx; AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); if (!pkt || !frame) { // 错误处理 return NULL; } while (1) { // 从队列中获取一个数据包(需要加锁) if (packet_queue_get(&my_ctx->queue, pkt) < 0) { // 队列已关闭,发送空包冲洗解码avcodec_send_packet(ctx, NULL); } else { // 发送数据包 int ret = avcodec_send_packet(ctx, pkt); av_packet_unref(pkt); // 无论成功与否,都释放数据包 if (ret < 0 && ret != AVERROR(EAGAIN)) { // 错误处理(非EAGAIN错误) break; } } // 接收 while (1) { int ret = avcodec_receive_frame(ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 如果是EAGAIN,需要更多数据,退出接收循环 // 如果是EOF,表示已经冲洗完成 if (ret == AVERROR_EOF) { // 设置标志,退出外层循环 } break; } else if (ret < 0) { // 其他错误 break; } // 处理,例如将放入显示队列 if (frame_queue_put(&my_ctx->frame_queue, frame) < 0) { // 队列已满或其他错误 av_frame_unref(frame); break; } av_frame_unref(frame); // 注意:队列中应该保存的是的副本或引用计数增加,这里我们假设队列内部会复制或增加引用 } // 如果已经发送了空包并且收到了EOF,则退出 if (/* 发送了空包并且已经处理完EOF */) { break; } } // 释放资源 av_packet_free(&pkt); av_frame_free(&frame); return NULL; } ``` 注意:上述代码中的队列操作(`packet_queue_get`和`frame_queue_put`)需要用户自己实现,并注意线程安全。 如果用户遇到特定的错误,比如崩溃、内存泄漏、解码等,可以针对性地提供更多信息。 由于问题中没有具体错误,我们只能给出一般性的建议。 最后,建议用户使用工具(如Valgrind)检查内存错误,并确保使用最新的FFmpeg版本。 如果问题仍然存在,请提供具体的错误信息或代码片段。 ### 相关问题 1. 如何正确使用FFmpeg的线程安全特性? 2. 如何实现一个高效的多线程视频解码器? 3. FFmpeg解码API(avcodec_send_packet/avcodec_receive_frame)的详细用法? 4. 如何检测和修复FFmpeg中的内存泄漏?</think>### 解码线程中 `void decode_thread` 函数的错误分析与修复 在 FFmpeg 解码线程中,`void decode_thread` 函数涉及 `AVCodecContext` 的使用,常见错误主要源于线程安全、资源管理和 API 使用当。以下是关键问题分析和解决方案: --- ### 常见错误及修复方案 #### 1. **线程安全问题** **错误现象**:多个线程同时访问同一个 `AVCodecContext` 导致崩溃或数据损坏。 **原因**:`AVCodecContext` 是线程安全对象,能跨线程共享。 **修复方案**: ```c // 错误:多个线程共享同一个 AVCodecContext // 正确:每个线程创建独立的上下文 void decode_thread(void *arg) { AVCodecContext *local_ctx = avcodec_alloc_context3(codec); if (avcodec_open2(local_ctx, codec, NULL) < 0) { // 错误处理 [^2] } // ... 解码操作 ... } ``` #### 2. **资源未正确释放** **错误现象**:内存泄漏或崩溃,尤其在退出线程时。 **原因**:未释放 `AVPacket`/`AVFrame` 或未关闭解码器。 **修复方案**: ```c void decode_thread(void *arg) { AVFrame *frame = av_frame_alloc(); AVPacket *pkt = av_packet_alloc(); while (1) { // 解码操作... av_packet_unref(pkt); // 释放数据包引用 av_frame_unref(frame); // 释放引用 } // 线程退出时释放资源 av_frame_free(&frame); av_packet_free(&pkt); avcodec_close(local_ctx); avcodec_free_context(&local_ctx); } ``` #### 3. **API 使用顺序错误** **错误现象**:解码失败,返回 `AVERROR(EAGAIN)` 或 `AVERROR_EOF`。 **原因**:未正确处理解码 API 的返回值。 **修复方案**(新 API `avcodec_send_packet`/`avcodec_receive_frame`): ```c while (1) { // 发送数据包 ret = avcodec_send_packet(local_ctx, pkt); if (ret == AVERROR(EAGAIN)) { // 需要先接收 [^1] continue; } // 接收 while (ret >= 0) { ret = avcodec_receive_frame(local_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; // 需更多数据或结束 // 处理... } } ``` #### 4. **未处理冲洗(Draining)阶段** **错误现象**:最后几丢失或解码器卡死。 **原因**:未发送 `NULL` 包冲洗解码器缓冲区。 **修复方案**: ```c // 数据包读取结束后冲洗解码avcodec_send_packet(local_ctx, NULL); // 发送空包 while (avcodec_receive_frame(local_ctx, frame) != AVERROR_EOF) { // 处理剩余 } ``` --- ### 完整线程安全解码流程 ```c void* decode_thread(void *arg) { // 1. 初始化线程独立资源 AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); AVCodecContext *ctx = avcodec_alloc_context3(codec); avcodec_open2(ctx, codec, NULL); AVFrame *frame = av_frame_alloc(); AVPacket *pkt = av_packet_alloc(); // 2. 解码循环 while (!thread_exit_flag) { if (get_packet_from_queue(pkt)) { // 从线程安全队列获取包 if (avcodec_send_packet(ctx, pkt) < 0) { // 错误处理 } av_packet_unref(pkt); } while (1) { int ret = avcodec_receive_frame(ctx, frame); if (ret == AVERROR(EAGAIN)) break; if (ret < 0) break; // 错误或EOF // 处理(如推送到渲染队列) push_frame_to_safe_queue(frame); av_frame_unref(frame); } } // 3. 冲洗解码器(见上文) // 4. 释放资源(见上文) return NULL; } ``` --- ### 关键注意事项 1. **线程隔离**:每个解码线程必须拥有独立的 `AVCodecContext` 和 `AVPacket`/`AVFrame`。 2. **错误处理**:始终检查 `avcodec_send_packet` 和 `avcodec_receive_frame` 的返回值。 3. **资源生命周期**: - `av_packet_alloc`/`av_frame_alloc` 与 `av_packet_free`/`av_frame_free` 配对使用 - 每次循环用 `av_packet_unref`/`av_frame_unref` 重置对象 4. **冲洗协议**:数据流结束后必须发送 `NULL` 包并处理残留。 > 调试建议:使用 `valgrind` 检查内存泄漏,启用 FFmpeg 日志 (`av_log_set_level(AV_LOG_DEBUG)`) 跟踪错误码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值