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ù)
- 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;
}
- 如源碼內(nèi)的備注1,備注2,備注3
- 接下來嘗試尋找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;
}
- 上圖源碼的備注4
- 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)上
- 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爸爸真香。