iOS硬解碼失敗問題

1. 問題

被安卓的小伙伴告知我們iOS拉服務(wù)端某ts流,出現(xiàn)一直黑屏的問題,發(fā)現(xiàn)是在VideoToolbox解碼時(shí)提示解碼失敗,返回值-12909(kVTVideoDecoderBadDataErr),奇怪之處還在于,有的ts流文件播放正常,于是使用ijk播放這個(gè)ts流,沒有問題,將ijk拉到的流與我們的sdk拉到的流分別寫264文件進(jìn)行比對,找到問題啦,如下圖


image.png

左邊是ijk的正常播放數(shù)據(jù),右邊是我們sdk拉下來的數(shù)據(jù),發(fā)現(xiàn)我們的流里每一幀后都有14字節(jié)(右圖紅色部分)未知數(shù)據(jù),于是猜測是ffmpeg讀出來的數(shù)據(jù)問題。

2. 定位

老規(guī)矩,源碼調(diào)試看看到底哪里增加的這項(xiàng)數(shù)據(jù)

  1. av_read_frame->read_frame_internal,發(fā)現(xiàn)是該函數(shù)的尾部調(diào)用av_packet_merge_side_data加入了這些多余的數(shù)據(jù),以下是該函數(shù)源碼實(shí)現(xiàn)
備注1:這里的FF_MERGE_MARKER宏定義就是我們多余數(shù)據(jù)的后8字節(jié) 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字節(jié)的多余數(shù)據(jù)放到了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. 如源碼內(nèi)的備注1,備注2,備注3
  2. 接下來嘗試尋找side_data_elems在哪里進(jìn)行的賦值,從read_frame_internal->ff_read_packet->s->iformat->read_packet,這里的iformat->read_packet是函數(shù)指針,指向的是mpegs.c里的mpegts_read_packet,接著它調(diào)用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暫時(shí)是一組0x00數(shù)據(jù),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; 此時(shí)這個(gè)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的位置將這些值替換,發(fā)現(xiàn)寫入的數(shù)據(jù)就是 E0 00 00 00 01 CE,正好與我們多余數(shù)據(jù)的前6字節(jié)完全對應(yīng)上
  3. 14字節(jié)的多余數(shù)據(jù)已全部定位到,那么要如何去除呢,我們來看看ijk內(nèi)部的實(shí)現(xiàn),發(fā)現(xiàn)ijk在解碼前會調(diào)用av_packet_split_side_data,這里就不貼該函數(shù)源碼了,因?yàn)橐呀?jīng)看到該函數(shù)內(nèi)部將尾部的多余數(shù)據(jù)刪除,于是在av_read_frame后也調(diào)用av_packet_split_side_data,解碼成功

3. 擴(kuò)展

調(diào)試源碼的過程中發(fā)現(xiàn)了nal_type = 0x09的數(shù)據(jù),之前沒怎么留意過,查資料得知,0x09是AUD(Access Unit Delimiter),訪問分隔單元,標(biāo)志著一幀的結(jié)束。

4. 總結(jié)

有ijk爸爸真香。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、總體說明 1.打開ijkplayer,可看到其主要目錄結(jié)構(gòu)如下: tool - 初始化項(xiàng)目工程腳本 confi...
    laixh閱讀 2,295評論 0 0
  • 相關(guān) x264編碼ffmpeg解碼ffmpeg編碼 問題 編解碼h264流時(shí),會發(fā)現(xiàn)末尾丟幀。以ffmpeg為例,...
    Don_閱讀 5,316評論 1 5
  • 因?yàn)镕Fmpeg更新的比較快,API也會跟著有所變動,所以聲明一下,本文使用的FFmpeg版本為V3.3.5。 1...
    片片碎閱讀 39,725評論 14 24
  • 1. FFMPEG 1.1基本組成: AVFormat: 封裝模塊,媒體封裝/解封裝格式,RTMP/RTSP/MM...
    xiaose26閱讀 799評論 0 1
  • 問題 主流程上的區(qū)別 緩沖區(qū)的設(shè)計(jì) 內(nèi)存管理的邏輯 音視頻播放方式 音視頻同步 seek的問題:緩沖區(qū)flush、...
    FindCrt閱讀 5,843評論 4 25

友情鏈接更多精彩內(nèi)容