iOS硬解码失败问题

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爸爸真香。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容