美文网首页
iOS硬解码失败问题

iOS硬解码失败问题

作者: doublesky_zhang | 来源:发表于2021-06-26 15:10 被阅读0次

    1. 问题

    被安卓的小伙伴告知我们iOS拉服务端某ts流,出现一直黑屏的问题,发现是在VideoToolbox解码时提示解码失败,返回值-12909(kVTVideoDecoderBadDataErr),奇怪之处还在于,有的ts流文件播放正常,于是使用ijk播放这个ts流,没有问题,将ijk拉到的流与我们的sdk拉到的流分别写264文件进行比对,找到问题啦,如下图


    image.png

    左边是ijk的正常播放数据,右边是我们sdk拉下来的数据,发现我们的流里每一帧后都有14字节(右图红色部分)未知数据,于是猜测是ffmpeg读出来的数据问题。

    2. 定位

    老规矩,源码调试看看到底哪里增加的这项数据

    1. av_read_frame->read_frame_internal,发现是该函数的尾部调用av_packet_merge_side_data加入了这些多余的数据,以下是该函数源码实现
    备注1:这里的FF_MERGE_MARKER宏定义就是我们多余数据的后8字节 8C 4D 9D 10 8E 25 E9 FE
    #define FF_MERGE_MARKER 0x8c4d9d108e25e9feULL
    
    int av_packet_merge_side_data(AVPacket *pkt){
        if(pkt->side_data_elems){
            AVBufferRef *buf;
            int i;
            uint8_t *p;
            uint64_t size= pkt->size + 8LL + AV_INPUT_BUFFER_PADDING_SIZE;
            AVPacket old= *pkt;
            for (i=0; i<old.side_data_elems; i++) {
                size += old.side_data[i].size + 5LL;
            }
            if (size > INT_MAX)
                return AVERROR(EINVAL);
            buf = av_buffer_alloc(size);
            if (!buf)
                return AVERROR(ENOMEM);
            pkt->buf = buf;
            pkt->data = p = buf->data;
            pkt->size = size - AV_INPUT_BUFFER_PADDING_SIZE;
            bytestream_put_buffer(&p, old.data, old.size);
            for (i=old.side_data_elems-1; i>=0; i--) {
    备注2:将side_data的data与size与type写到pkt->data的后面
                bytestream_put_buffer(&p, old.side_data[i].data, old.side_data[i].size);
                bytestream_put_be32(&p, old.side_data[i].size);
                *p++ = old.side_data[i].type | ((i==old.side_data_elems-1)*128);
            }
    备注3:将FF_MERGE_MARKER8字节的多余数据放到了pkt->data的尾部
            bytestream_put_be64(&p, FF_MERGE_MARKER);
            av_assert0(p-pkt->data == pkt->size);
            memset(p, 0, AV_INPUT_BUFFER_PADDING_SIZE);
            av_packet_unref(&old);
            pkt->side_data_elems = 0;
            pkt->side_data = NULL;
            return 1;
        }
        return 0;
    }
    
    1. 如源码内的备注1,备注2,备注3
    2. 接下来尝试寻找side_data_elems在哪里进行的赋值,从read_frame_internal->ff_read_packet->s->iformat->read_packet,这里的iformat->read_packet是函数指针,指向的是mpegs.c里的mpegts_read_packet,接着它调用new_pes_packet->av_packet_new_side_data->av_packet_add_side_data将pkt->side_data_elems++,源代码如下:
    int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
                                uint8_t *data, size_t size)
    {
        AVPacketSideData *tmp;
        int i, elems = pkt->side_data_elems;
    
        for (i = 0; i < elems; i++) {
            AVPacketSideData *sd = &pkt->side_data[i];
    
            if (sd->type == type) {
                av_free(sd->data);
                sd->data = data;
                sd->size = size;
                return 0;
            }
        }
    
        if ((unsigned)elems + 1 > AV_PKT_DATA_NB)
            return AVERROR(ERANGE);
    
        tmp = av_realloc(pkt->side_data, (elems + 1) * sizeof(*tmp));
        if (!tmp)
            return AVERROR(ENOMEM);
    
    备注4: 这里的data暂时是一组0x00数据,size为1,type为AV_PKT_DATA_MPEGTS_STREAM_ID(0x4E)
        pkt->side_data = tmp;
        pkt->side_data[elems].data = data;
        pkt->side_data[elems].size = size;
        pkt->side_data[elems].type = type;
        pkt->side_data_elems++;
    
        return 0;
    }
    
    1. 上图源码的备注4
    2. new_pes_packet最后一行*sd = pes->stream_id; 此时这个sd指向的是pkt->side_data[elems].data,于是这里我们可以知道pkt->side_data[0].data为pes包的stream_id(这里为视频流,因此它为0xE0),pkt->side_data[elems].size = 1,pkt->side_data[elems].type = 0x4E,再来到备注2的位置将这些值替换,发现写入的数据就是 E0 00 00 00 01 CE,正好与我们多余数据的前6字节完全对应上
    3. 14字节的多余数据已全部定位到,那么要如何去除呢,我们来看看ijk内部的实现,发现ijk在解码前会调用av_packet_split_side_data,这里就不贴该函数源码了,因为已经看到该函数内部将尾部的多余数据删除,于是在av_read_frame后也调用av_packet_split_side_data,解码成功

    3. 扩展

    调试源码的过程中发现了nal_type = 0x09的数据,之前没怎么留意过,查资料得知,0x09是AUD(Access Unit Delimiter),访问分隔单元,标志着一帧的结束。

    4. 总结

    有ijk爸爸真香。

    相关文章

      网友评论

          本文标题:iOS硬解码失败问题

          本文链接:https://www.haomeiwen.com/subject/zjhzyltx.html