前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >视频又又又又花屏了

视频又又又又花屏了

作者头像
程序员的园
发布2025-03-07 10:02:47
发布2025-03-07 10:02:47
3100
代码可运行
举报
运行总次数:0
代码可运行

本文来源于生产环境的实际问题,值得大家一看。

问题背景

鉴于背景的复杂性,将该问题的背景简述如下:客户端A将桌面采集的数据以H264的格式推送到服务器B,服务器B将数据转发到客户端C,客户端C接收数据解码渲染。客户端C渲染完的数据存在花屏。

花屏的同时,客户端C会一直收到H264解码器的报错信息,

代码语言:javascript
代码运行次数:0
复制
[h264 @ 0x1690000] No start code is found.
[h264 @ 0x1690000] Error splitting the input into NAL units.

问题分析

前置知识

  • 在之前的章节中,已经介绍过了音视频处理的流程:采集、编码、传输、解码、渲染。见文章音视频处理流程,其中的任何一个环节出现问题都会导致花屏。
  • H264编码后的数据,是以 NALU(Network Abstraction Layer Unit) 为单位进行传输的,每个 NALU 前面都会有一个起始码,起始码为 0x00000001 或者 0x000001 ,解码器就是通过这个起始码来找到 NALU 的边界的,所述起始码也就是上述报错中的 start code。

定位问题

结合日志,解码器提示No start code is found.,则说明解码器接收到的 H264 流找不到对应的 start code,解码器收到的 H264 流存在问题,所以为了验证传输的数据流的完整性和正确性,

首先想到的是使用第三方工具接收对应的视频流,然后播放。同时使用了 ffplay 、 vlc ,两者均未出现花屏,且均能正常播放。这足以证明客户端C接收到的 H264 流是正确且完整的。

但是解码器依然报错,那问题点可以锁定在 H264 流送给解码器过程存在问题。所以分析的重点便是 H264流送给解码器 这一过程。

结合读者朋友的代码逻辑,其将接收到数据的H264流作为一个packet传送给解码器,代码如下:

代码语言:javascript
代码运行次数:0
复制
auto packet = av_packet_alloc();
auto pFrame = av_frame_alloc();
packet->data = (uint8_t*)data;
packet->size = dataLen;
auto ret = avcodec_send_packet(pCodecCtx, packet);
//other code

如上的代码直接将接收到的数据作为一个 packet 传送给解码器,但是我们并不能保证接收到的数据一定是一个完整的NALU,还可能是多个NALU,所以问题的根本原因为:接收到的数据并不是数据包,不能直接用来解码

故此接收到的数据需要经过解析器解析,将数据拆分成一个个完整的 NALU ,再传送给解码器。

FFmpeg提供了解析器相关的接口av_parser_parse2(),用于将流数据解析为一个个的数据包,接口如下:

代码语言:javascript
代码运行次数:0
复制
//功能:解析数据流,
//参数:s:解析器上下文,
//     avctx:解码器上下文,
//     poutbuf:解析后的数据包,
//     poutbuf_size:解析后的数据包大小,
//     buf:待解析的数据,
//     buf_size:待解析的数据大小,
//     pts:时间戳,
//     dts:解码时间戳,
//     pos:数据包在文件中的位置
//返回值:解析数据时所使用的字节数,
int av_parser_parse2(AVCodecParserContext *s,
                     AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts,
                     int64_t pos);

解决问题

根据上述分析,为解决如上问题需要借助解析器将接收到的数据拆分成一个个完整的 NALU ,再传送给解码器,代码如下:

代码语言:javascript
代码运行次数:0
复制
// 初始化解析器
auto pParser = av_parser_init(pCodecCtx->codec_id);
if (!pParser) {
    std::cerr << "Failed to initialize parser." << std::endl;
    return-1;
}

// 分配AVPacket和AVFrame
auto packet = av_packet_alloc();
auto pFrame = av_frame_alloc();
if (!packet || !pFrame) {
    std::cerr << "Failed to allocate packet or frame." << std::endl;
    return-1;
}

// 获取数据长度和指针
auto dataLen = data.size(); // data作为std::vector<uint8_t>
auto dataPtr = data.data();
int dataRemain = dataLen;

// 循环解析数据流
while (dataRemain > 0) {
    int parsedLen = av_parser_parse2(pParser, pCodecCtx, &packet->data, &packet->size,
                                     dataPtr, dataRemain, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
    if (parsedLen < 0) {
        std::cerr << "Error occurred during parsing." << std::endl;
        return-1;
    }

    dataPtr += parsedLen;
    dataRemain -= parsedLen;
    if (packet->size > 0) {
        // 将解析后的NALU传递给解码器
        int ret = avcodec_send_packet(pCodecCtx, packet);
        if (ret < 0) {
            std::cerr << "Error sending packet to decoder." << std::endl;
            return-1;
        }

        // 循环接收解码后的帧
        while (true) {
            ret = avcodec_receive_frame(pCodecCtx, pFrame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break; // 没有更多帧可解码
            } elseif (ret < 0) {
                std::cerr << "Error during frame decoding." << std::endl;
                return-1;
            }
            // 处理解码后的帧,例如保存到队列中
        }
    }
}

// 释放资源
av_packet_free(&packet);
av_frame_free(&pFrame);
av_parser_close(pParser);

总结

本文记录了一个线上问题的分析和解决过程。问题的根本原因是客户端C直接将未解析的H.264流传递给解码器,而没有正确分割 NALU 。通过使用av_parser_parse2对数据流进行解析,可以将原始数据流拆分成完整的 NALU ,从而解决解码器报错的问题。

在生产环境中,建议在数据传输和解码前,始终对数据流进行解析,确保传递给解码器的是完整的 NALU 。此外,通过日志分析和逐步调试,可以快速定位问题,避免因数据格式问题导致的花屏或解码失败。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题背景
  • 问题分析
    • 前置知识
    • 定位问题
    • 解决问题
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档