目录

解码器解码过程中卡住无输出

问题现象

在调试解码器硬解功能时发现反复拉流的时候解码器有段时间只能输入数据,但是没有数据输出。如下图所示:

背景知识

视频解码中的调用关系图如下:

问题定位

  1. 当前仅支持传入AnnexB视频码流,不支持AVCC格式码流,检查传入的码流格式是否符合。
  2. 确保buffersize大小正确传入。
  3. 解码器Flush后,再次启动解码器,需要传入XPS和关键帧,否则直到传入XPS和关键帧为止,相关帧将会被丢弃。排查首帧是否未传XPS信息。
  4. 发现AVCODEC_BUFFER_FLAGS_CODEC_DATA未传准确,但是修改后问题还是存在。
  5. 再次测试,错误日志如下:

  6. 根据解码输出出现问题的时间段定位到11:37的时间点。查看日志输出相关时间点发现报错如下:
    10-14 11:37:43.461 1561 27622 E C02B32/av_codec_service/HCODEC: [264][dec.avc][Running][OnQueueInputBuffer 865] wrong ownership: buffer id=3, owner=omx
    10-14 11:37:43.461 1561 27622 E C02B32/av_codec_service/HCODEC: [264][dec.avc][Running][OnQueueInputBuffer 865] wrong ownership: buffer id=3, owner=omx
    10-14 11:37:43.462 1561 27622 E C02B32/av_codec_service/HCODEC: [264][dec.avc][Running][OnQueueInputBuffer 865] wrong ownership: buffer id=3, owner=omx

分析结论

根据日志结果在此时间段有多次传入索引为3的buffer数据。分析发现此时间段内并没有接收到系统的回调,就将这几帧buffer给push传入,导致前面几帧push失败。

不可同时向解码器push多次同一index和buffer,正常情况下再次向同一个缓冲区填充数据传入队列需要等待解码器消费完数据并被释放后发起OH_AVCodecOnNeedInputBuffer回调,用该回调的index和buffer指针成为参数来填充数据以此实现轮转。

修改建议

对解码器的输入队列进行上锁操作,在输入队列中有数据并要将其传入解码器时确保锁保护共享资源访问,获得锁后从解码输入队列中获取回调得到的index和buffer缓冲区出队列,后释放锁,填充帧数据到buffer后使用PushInputBuffer送入解码器解码。

void Player::VideoDecInputThread()
{
    while (true) {
        CHECK_AND_BREAK_LOG(isStarted_, "Decoder input thread out");
        std::unique_lock<std::mutex> lock(videoDecContext_->inputMutex);
        bool condRet = videoDecContext_->inputCond.wait_for(
            lock, 5s, [this]() { return !isStarted_ || !videoDecContext_->inputBufferInfoQueue.empty(); });
        CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
        CHECK_AND_CONTINUE_LOG(!videoDecContext_->inputBufferInfoQueue.empty(),
                               "Buffer queue is empty, continue, cond ret: %{public}d", condRet);

        CodecBufferInfo bufferInfo = videoDecContext_->inputBufferInfoQueue.front();
        videoDecContext_->inputBufferInfoQueue.pop();
        videoDecContext_->inputFrameCount++;
        lock.unlock();

        demuxer_->ReadSample(demuxer_->GetVideoTrackId(), reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer),
                             bufferInfo.attr);

        int32_t ret = videoDecoder_->PushInputBuffer(bufferInfo);
        AVCODEC_SAMPLE_LOGW("PushInputBuffer info index:"
                            "%{public}u  framecount: %{public}u, pts: %{public}" PRId64,
                            bufferInfo.bufferIndex, videoDecContext_->inputFrameCount, bufferInfo.attr.pts);
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Push data failed, thread out");

        CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out");
    }
}

index输出如下图所示:

完整demo可参考AVCodecVideo。

总结

使用VideoDecoder_PushInputBuffer通知视频解码器已对index对应的缓冲区完成输入数据的填充时,输入回调AVCodecOnNeedInputBuffer将报告可用的输入缓冲区和相应的index值。一旦具有指定index的缓冲区提交到视频解码器,则无法再次访问缓冲区,直到再次收到输入回调,报告具有相同index的缓冲区可用。