H264 AVCC 格式转 ANNEX B格式

概述

有很多封装格式的H264码流采用的是AVCC格式。有些硬件编码器仅支持ANNEX B格式的H264码流,因此就需要做一次中转,本示例主要展示如何转换。

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#define NALU_TYPE_SLICE 1
#define NALU_TYPE_DPA 2
#define NALU_TYPE_DPB 3
#define NALU_TYPE_DPC 4
#define NALU_TYPE_IDR 5
#define NALU_TYPE_SEI 6
#define NALU_TYPE_SPS 7
#define NALU_TYPE_PPS 8
#define NALU_TYPE_AUD 9
#define NALU_TYPE_EOSEQ 10
#define NALU_TYPE_EOSTREAM 11
#define NALU_TYPE_FILL 12

/*
H264帧由NALU头和NALU主体组成。
NALU头由一个字节组成,它的语法如下:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |F|NRI|  Type   |
  +---------------+

F: 1个比特.
  forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.

NRI: 2个比特.
  nal_ref_idc. 取00~11,似乎指示这个NALU的重要性,
  如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,
  取值越大,表示当前NAL越重要,需要优先受到保护.
  如果当前NAL是属于参考帧的片,或是序列参数集,
  或是图像参数集这些重要的单位时,本句法元素必需大于0。

Type: 5个比特.
  nal_unit_type. 这个NALU单元的类型,1~12由H.264使用,
  24~31由H.264以外的应用使用.
 */
typedef struct {
	uint8_t nal_type:5;
	uint8_t nal_ref_idc:2;
	uint8_t forbidden_zero_bit:1;
} H264Hdr;

int parase_nalu_hdr(uint8_t *nal_hdr, int nal_len) {
	const char *type_str = NULL;
	H264Hdr *hdr = (H264Hdr *)nal_hdr;
	printf("### DUMP nalu hdr(hdr:0x%02x, len=%d):\n",*nal_hdr, nal_len);
	printf("forbidden_zero_bit:%d\n", hdr->forbidden_zero_bit);
	printf("nal_ref_idc:%d\n", hdr->nal_ref_idc);
	switch (hdr->nal_type) {
		case NALU_TYPE_SLICE:
			type_str = "P/B Slice";
			break;
		case NALU_TYPE_AUD:
			type_str = "AUD Slice";
			break;
		case NALU_TYPE_DPA:
			type_str = "DPA Slice";
			break;
		case NALU_TYPE_DPB:
			type_str = "DPB Slice";
			break;
		case NALU_TYPE_DPC:
			type_str = "DPC Slice";
			break;
		case NALU_TYPE_EOSEQ:
			type_str = "EOS seq Slice";
			break;
		case NALU_TYPE_EOSTREAM:
			type_str = "EOS stream Slice";
			break;
		case NALU_TYPE_FILL:
			type_str = "FILL Slice";
			break;
		case NALU_TYPE_IDR:
			type_str = "IDR Slice";
			break;
		case NALU_TYPE_PPS:
			type_str = "PPS Slice";
			break;
		case NALU_TYPE_SPS:
			type_str = "SPS Slice";
			break;
		case NALU_TYPE_SEI:
			type_str = "SEI Slice";
			break;
		default:
			type_str = "Unknow Slice";
			printf("Warn: nal type:%d\n", hdr->nal_type);
			break;
	}
	printf("nal_type:%s\n", type_str);
	return 0;
}

/*
 bits:  
	8   version ( always 0x01 )
	8   avc profile ( sps[0][1] )
	8   avc compatibility ( sps[0][2] )
	8   avc level ( sps[0][3] )
	6   reserved ( all bits on )
	2   NALULengthSizeMinusOne  // 这个值是(前缀长度-1),值如果是3,那前缀就是4,因为4-1=3
	3   reserved ( all bits on )
	5   number of SPS NALUs (usually 1)
repeated once per SPS:
	16     SPS size
	variable   SPS NALU data
	8   number of PPS NALUs (usually 1)
repeated once per PPS
	16    PPS size
	variable PPS NALU data
 */

typedef struct {
	uint8_t version;
	uint8_t avc_profile;
	uint8_t avc_compatibility;
	uint8_t avc_level;
	uint8_t reserved0:6;
	uint8_t nalu_len:2;
} AvccExtraHdr;

#define SPS_CNT_MASK 0x1F

int parase_avcc_extra(char *buff, int size) {
	uint8_t sps_cnt;
	uint16_t sps_size;
	uint8_t pps_cnt;
	uint16_t pps_size;
	uint8_t *offset = NULL;
	uint8_t extra_to_anexb[1024] = {0x00, 0x00, 0x00, 0x01, 0x00};

	if (!buff || (size < 7))
		return -1;

	AvccExtraHdr *hdr = (AvccExtraHdr *)buff;
	printf("version:0x%02x\n", hdr->version);
	printf("avc_profile:0x%02x\n", hdr->avc_profile);
	printf("avc_compatibility:0x%02x\n", hdr->avc_compatibility);
	printf("avc_level:0x%02x\n", hdr->avc_level);
	printf("reserved0:0x%02x\n", hdr->reserved0);
	printf("nalu_len:0x%02x\n", hdr->nalu_len);

	FILE *file_out = fopen("annexb.h264", "wb");
	if (!file_out) {
		printf("ERROR: open annexb.h264 failed!\n");
		return -1;
	}
	fseek(file_out, 0, SEEK_SET);

	offset = (uint8_t *)buff + sizeof(AvccExtraHdr);
	sps_cnt = *offset & SPS_CNT_MASK;
	offset++;
	printf("sps_cnt:%d\n", sps_cnt);
	for (int i = 0; i < sps_cnt; i++) {
		// 网络字节序转为主机字节序
		sps_size = ntohs(*((uint16_t *)offset));
		offset += 2;
		printf("+++ FLC-DBG: write anexb hdr to annexb.h264...\n");
		fwrite(extra_to_anexb, 1, 4, file_out);
		printf("+++ FLC-DBG: write sps:0x%02x to annexb.h264...\n", *offset);
		fwrite(offset, 1, sps_size, file_out);
		printf("[%d] sps_size:%d, sps_data:", i, sps_size);
		for (int j = 0; j < sps_size; j++)
			printf("0x%02x ", *(offset++));
		printf("\n");
	}
	pps_cnt = *(offset++);
	printf("pps_cnt:%d\n", pps_cnt);
	for (int i = 0; i < sps_cnt; i++) {
		// 网络字节序转为主机字节序
		pps_size = ntohs(*((uint16_t *)offset));
		offset += 2;
		printf("+++ FLC-DBG: write anexb hdr to annexb.h264...\n");
		fwrite(extra_to_anexb, 1, 4, file_out);
		printf("+++ FLC-DBG: write sps:0x%02x to annexb.h264...\n", *offset);
		fwrite(offset, 1, pps_size, file_out);
		printf("[%d] pps_size:%d, pps_data:", i, pps_size);
		for (int j = 0; j < pps_size; j++)
			printf("0x%02x ", *(offset++));
		printf("\n");
	}

	fclose(file_out);

	return 0;
}

#define PARASE_MODE_EXTRA 1
#define PARASE_MODE_STREAM 2

int main(int argc, char *argv[]) {
	int mode = 0;
	if (argc < 3) {
		printf("ERROR: Invalid args\n");
		printf("Usage: \n\t1.%s -extra avcc_extra.bin\n", argv[0]);
		printf("\t2.%s -stream avcc_stream.bin\n", argv[0]);
		return -1;
	}

	if (!strcmp(argv[1], "-extra"))
		mode = PARASE_MODE_EXTRA;
	else if (!strcmp(argv[1], "-stream"))
		mode = PARASE_MODE_STREAM;
	else {
		printf("ERROR: Mode invalid\n");
		return -1;
	}

	printf("# InFile:%s\n", argv[2]);
	FILE *file = fopen(argv[2], "r");
	if (!file) {
		printf("ERROR: open %s failed!\n", argv[2]);
		return -1;
	}

	fseek(file, 0, SEEK_END);
	int flen = ftell(file);
	if (flen <= 0) {
		printf("ERROR: file length invalid! flen:%d\n", flen);
		fclose(file);
		return -1;
	}

	uint8_t *buffer = (uint8_t *)malloc(flen + 1);
	if (!buffer) {
		printf("ERROR: malloc %d Bytes failed!\n", flen);
		fclose(file);
		return -1;
	}

	fseek(file, 0, SEEK_SET);
	int ret = fread(buffer, 1, flen, file);
	if (ret <= 0) {
		printf("ERROR: read file %d Bytes error!\n", flen);
		fclose(file);
		free(buffer);
		return -1;
	}
	fclose(file);

	if (mode == PARASE_MODE_EXTRA) {
		printf("#### PARASE AVCC EXTRA DATA ####\n");
		ret = parase_avcc_extra(buffer, ret);
		if (ret < 0)
			printf("ERROR: parase avcc data failed!\n");
	} else {
		printf("#### PARASE AVCC STREAM ####\n");
		// 解析avcc h264码流,长度默认:4字节,可根据extra data的nalu_len来计算。
		int offset = 0;
		int nal_len = 0;
		while (offset < flen) {
			nal_len = ntohl(*((uint32_t *)(buffer + offset)));
			// 将avcc的4字节长度转换为annex b的“00 00 00 01”分隔符。
			*(buffer + offset) = 0;
			*(buffer + offset + 1) = 0;
			*(buffer + offset + 2) = 0;
			*(buffer + offset + 3) = 1;
			offset += 4;
			parase_nalu_hdr((uint8_t *)(buffer + offset), nal_len);
			offset += nal_len;
		}
		// 追加形式打开文件,保存转换后的Stream。
		FILE *file_out = fopen("annexb.h264", "a+");
		if (!file_out) {
			printf("ERROR: open annexb.h264 failed!\n");
			fclose(file);
			free(buffer);
			return -1;
		}

		fwrite(buffer, 1, flen, file_out);
		fclose(file_out);
	}

	free(buffer);
	return ret;
}

用法

1、先从extra信息中解析出sps跟pps。

./main -extra avcc_extradata.bin
在这里插入图片描述
2、转换avcc的码流为annex B格式。

./main -stream avcc_stream.bin
在这里插入图片描述

完整资源包下载

https://download.csdn.net/download/lyy901135/12347498

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
H.264 封装为 MP4 的源码可以比较复杂,需要涉及到视频编码、封装格式等多个方面的知识。以下是一个简单的示例代码,可以实现将 H.264 裸流封装为 MP4 文件的功能: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define FRAME_RATE 25 #define BIT_RATE 4800000 typedef struct { FILE *fp; int width; int height; int frame_rate; int bit_rate; int gop_size; int frame_count; } MP4File; typedef struct { unsigned char *data; int size; int key_frame; int pts; } H264Frame; static void write_int(FILE *fp, int value) { unsigned char buf[4]; buf[0] = value >> 24; buf[1] = value >> 16; buf[2] = value >> 8; buf[3] = value; fwrite(buf, 1, 4, fp); } static void write_short(FILE *fp, short value) { unsigned char buf[2]; buf[0] = value >> 8; buf[1] = value; fwrite(buf, 1, 2, fp); } static void write_data(FILE *fp, unsigned char *data, int size) { fwrite(data, 1, size, fp); } MP4File *mp4_create(const char *filename, int width, int height, int frame_rate, int bit_rate) { MP4File *mp4 = (MP4File *)malloc(sizeof(MP4File)); memset(mp4, 0, sizeof(MP4File)); mp4->fp = fopen(filename, "wb"); if (!mp4->fp) { printf("failed to create mp4 file\n"); free(mp4); return NULL; } mp4->width = width; mp4->height = height; mp4->frame_rate = frame_rate; mp4->bit_rate = bit_rate; mp4->gop_size = frame_rate * 2; write_data(mp4->fp, (unsigned char *)"ftypisom", 8); write_int(mp4->fp, 0); write_data(mp4->fp, (unsigned char *)"isom", 4); write_data(mp4->fp, (unsigned char *)"mp42", 4); return mp4; } void mp4_destroy(MP4File *mp4) { if (mp4) { fclose(mp4->fp); free(mp4); } } void mp4_write_frame(MP4File *mp4, H264Frame *frame) { if (!mp4 || !frame) { return; } if (frame->key_frame) { write_data(mp4->fp, (unsigned char *)"mdat", 4); write_int(mp4->fp, 0); } int size = frame->size + 4; write_int(mp4->fp, size); if (frame->key_frame) { write_data(mp4->fp, (unsigned char *)"avc1", 4); write_short(mp4->fp, 0); write_short(mp4->fp, 0); write_short(mp4->fp, mp4->width); write_short(mp4->fp, mp4->height); write_int(mp4->fp, mp4->bit_rate); write_int(mp4->fp, mp4->frame_rate); write_short(mp4->fp, 1); write_short(mp4->fp, 12); write_data(mp4->fp, (unsigned char *)"avcC", 4); write_int(mp4->fp, 0x01); write_data(mp4->fp, frame->data + 4, 3); write_short(mp4->fp, 0xffe1); write_short(mp4->fp, 16); write_data(mp4->fp, (unsigned char *)"avcC", 4); write_data(mp4->fp, frame->data + 7, frame->size - 7); } else { write_data(mp4->fp, frame->data, frame->size); } mp4->frame_count++; } int main() { MP4File *mp4 = mp4_create("test.mp4", 1280, 720, FRAME_RATE, BIT_RATE); if (!mp4) { return 0; } FILE *h264 = fopen("test.h264", "rb"); if (!h264) { mp4_destroy(mp4); return 0; } H264Frame frame; memset(&frame, 0, sizeof(H264Frame)); frame.data = (unsigned char *)malloc(1024 * 1024); while (!feof(h264)) { size_t size = fread(frame.data, 1, 1024 * 1024, h264); frame.size = size; frame.key_frame = 1; frame.pts = mp4->frame_count * 90000 / mp4->frame_rate; mp4_write_frame(mp4, &frame); } free(frame.data); fclose(h264); mp4_destroy(mp4); return 0; } ``` 在这个示例中,我们首先定义了一个 MP4File 结构体,用于保存 MP4 文件的相关信息,比如宽高、帧率、码率等。mp4_create 函数用于创建 MP4 文件,它会写入 MP4 文件的头信息。mp4_write_frame 函数用于写入 H.264 数据帧,它会根据帧类型(关键帧或非关键帧)来写入相应的 MP4 Box。最后,在主函数中我们读入 H.264 裸流,并将其封装为 MP4 文件。 需要注意的是,这里只是一个简单的示例,实际的 MP4 封装格式可能比这个复杂得多,还需要考虑音频、时间戳等问题。如果需要进行更加复杂的封装,建议使用现有的一些开源封装库,比如 FFmpeg 等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值