FFmpeg解析mp4中H.264 码流

FFmpeg解析mp4中H.264 码流

MP4文件中编码信息是存储在文件开始或者文件末尾的,详细结构这里不详述了。就知道不是和图像数据放在一起的就可以了。
FFmpeg使用av_read_frame(AVFormatContext *s, AVPacket *pkt)函数读mp4文件,读到packet里面仅仅是VCL编码数据NAL,并且这个编码数据是AVCC格式组织的码流,直接保存成.264文件没法播放。
先说一下思路:
1 .从avFmtCtx->streams[_videoStreamIndex]->codecpar->extradata中解析SPS和PPS数据,数据格式上一节已经描述了。解析出SPS PPS数据加上4字节的0001的起始码拼装成nnexB格式的NALU,先写入文件。
2. 通过av_read_frame(AVFormatContext *s, AVPacket *pkt)读取到数据存放在pkt->data中,长度为pkt->size。

注意:这1个pkt->data中的数据可能是多个NALU的数据!!!这些数据按([length] NALU) | ([length] NALU) | …规则排列。先取前4字节作为长度,读取指定长度的数据加上起始码拼NALU。然后同样的方式读取后面的数据,直到总长度等于pkt->size。

FFmpeg 解析mp4中H264码流 代码示例
这里就只贴关键部分代码。省略前面打开文件和查询流信息等操作。

	//...
    AVPacket spsPacket, ppsPacket, tmpPacket;
    uint8_t startCode[4] = {0x00, 0x00, 0x00, 0x01};
    bool sendSpsPps = false;

    while (av_read_frame(_avFmtCtx, _avPacket) == 0) { // 能读到数据返回0,循环读取
    	// 根据pkt->stream_index判断是不是视频流
        if (_avPacket->stream_index == _videoStreamIndex) {
        	// 仅1次处理sps pps,也可以拿在while外面
            if (!sendSpsPps) { 
                int spsLength = 0;
                int ppsLength = 0;
                // extradata 数据指针,方便操作取其指针
                uint8_t *ex = _avFmtCtx->streams[_videoStreamIndex]->codecpar->extradata;
				
				// extradata;第6字节后5位表示SPS个数,通常为1,这里就省略判断处理,严谨期间还是要判断
				// 直接 取第7 8 俩字节作为SPS长度
                spsLength = (ex[6] << 8) | ex[7];
				
				// x[8+spsLength]表示PPS个数,通常为1,这里就省略判断处理
				// 取接下来两位作为PPS长度
                ppsLength = (ex[8 + spsLength + 1] << 8) | ex[8 + spsLength + 2];

				// 为spsPacket ppsPacket的data分配内存,类似malloc
				// 如果只是为了保存文件,可以不使用pkt结构,直接malloc就行
				// 分配的空间为sps或pps长度加上4字节的起始码
                av_new_packet(&spsPacket, spsLength + 4);
                av_new_packet(&ppsPacket, ppsLength + 4);
				
				// 给SPS拼前4字节起始码
                memcpy(spsPacket.data, startCode, 4);
                // 把SPS数据拼在起始码后面
                memcpy(spsPacket.data + 4, ex + 8, spsLength);
				
				// TODO: 这里可以把spsPacket.data数据写入文件 
				
				// 给PPS拼前4字节起始码
                memcpy(ppsPacket.data, startCode, 4);
                // 把PPS数据拼在起始码后面
                memcpy(ppsPacket.data + 4, ex + 8 + spsLength + 2 + 1, ppsLength);
                
				// TODO: 这里可以把ppsPacket.data数据写入文件 
				
                sendSpsPps = true;
            }

			// 下面处理读到pkt中的数据
            int nalLength = 0;
            uint8_t *data = _avPacket->data;
            // _avPacket->data中可能有多个NALU,循环处理
            while (data < _avPacket->data + _avPacket->size) {
            	// 取前4字节作为nal的长度
                nalLength = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
                if (nalLength > 0) {
                    memcpy(data, startCode, 4);  // 拼起始码
                    tmpPacket = *_avPacket;      // 仅为了复制packet的其他信息,保存文件可忽略
                    tmpPacket.data = data;		 // 把tmpPkt指针偏移到实际数据位置
                    tmpPacket.size = nalLength + 4; // 长度为nal长度+起始码4
					
					//TODO: 处理这个NALU的数据,可以直接把tmpPacket.data写入文件
                }
                data = data + 4 + nalLength; // 处理data中下一个NALU数据
            }
        }

        av_packet_unref(_avPacket);
    }


你可能感兴趣的:(音视频,音视频)