H264和AAC

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了H264和AAC相关的知识,希望对你有一定的参考价值。

参考技术A 注:本文比较浅显,适合初学者看。

引述:H264的标准已经很多年了,成熟稳定,普及率非常高,目前比它更好的也有H265和VP9(google出品),但都不能撼动H264的地位。

A: 

h264( MPEG -4 Part 10), 对应的H265就是( MPEG -4 Part 11)

1、BP-Baseline Profile:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;

2、EP-Extended profile:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;

3、MP-Main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),

也支持CAVLC 和CABAC 的支持;

在BP-Baseline Profile基础上另外增加了:

- 隔行编码

- B条带类型

- CABAC熵编码

- 加权预测

- 422和444 YCbCr

- 不支持ASO, FMO和RS

4、HP-High profile:高级画质。在main Profile 的基础上增加了

- 8x8内部预测、4*4和8*8大小之间自适应选择的支持。

- 自定义量化、 

- 可预测无损视频编码

- 更多的YUV 格式;

- 高级10档次(Hi10P), 高级422,444档次;

Baseline和Main比较常用,Baseline 压缩的比较厉害,低于mepg-4的10%-20%; Main低于MPEG-4的50%.  High比Main又能节约10% 的码流量,那High应该是低于mepg-4的45%。

压缩率来说就是BP>HP>MP, 比例大概是100的原始数据, 压缩后BP=15,HP=45,MP=50;

所以MP和HP其实没啥大区别,但是BP就压缩的很低,所以一般监控和直播会首选BP,BP的特点是没有B帧,画质很一般,能满足基本要求。

【B帧的前世今生】

除了Baseline以外,其它都是有B帧的,I/B/P的关系用一句话说就是:I就是一张完整图片,但数据很大,如果都是I帧,视频会很庞大,而且也没必要;P帧是单向参考,前向参考,只参考I帧的,记录和I帧的区别;B帧双向参考,参考I帧和P帧,如果被参考的P帧花屏了,B帧也会花屏。

B帧会带来一个问题:编码顺序和解码顺序不同,因为正常播放时,解码帧的排列是I BBB... P BBB... P

可以看出B帧是插在IP和PP之间的,因为B帧数据比较小,所以会插很多帧。

我们来看看编码的顺序是怎样的:I P BBB...

解码顺序呢:I BBB...P

这问题了来,有B帧时编码解码BP的顺序不一致,所以引入了PTS(显示时间戳)和DTS(解码时间戳)

贴一张装B的图(先留着,我也没仔细看):

B:

h264的功能分为两层,视频编码层(VCL)和网络提取层(NAL)

VCL功能是进行视频编解码,包括运动补偿预测,变换编码和熵编码等功能;

NAL用于采用适当的格式对VCL视频数据进行封装打包

1)VCL数据即被压缩编码后的视频数据序列。

在VCL数据要封装到NAL单元中之后,才可以用来传输或存储。

VCL里面的几个基本概念就是: YCbCr颜色空间, 宏块,内循环去块过滤器,条带,内部预测,运动补偿,变换、缩放和量化;熵编码

2)NAL单格式(NALU)

H.264定义了两种封装格式: RTP头和start_code_prefix头(00 00 01 和 00 00 00 01)

主要目的是确定NAL边界(其实封装都是这个目的,大同小异)。

包含针对rtp/ip, 文件格式如mp4, 服务视频会议的h.32x, 和mpeg-2系统

参考:《视频技术手册(第5版)》

MP4V2封装的类库,可将H264和AAC直接打包到MP4容器中,堪称经典

废话少说,直接上代码:

// MP4Encoder.h
#pragma once

#ifndef _MP4V2_H_
#define _MP4V2_H_
#include "mp4v2/mp4v2.h"
#endif

#define MP4ENCODER_ERROR(err) ((MP4EncoderResult)(-(err)))
#define DEFAULT_RECORD_TIME 0U

typedef enum

	MP4ENCODER_ENONE = 0,
	MP4ENCODER_E_CREATE_FAIL,
	MP4ENCODER_E_ADD_VIDEO_TRACK,
	MP4ENCODER_E_ADD_AUDIO_TRACK,
	MP4ENCODER_WARN_RECORD_OVER,
	MP4ENCODER_E_WRITE_VIDEO_DATA,
	MP4ENCODER_E_WRITE_AUDIO_DATA,
	MP4ENCODER_E_ALLOC_MEMORY_FAILED,
	MP4ENCODER_E_UNKONOWN
MP4EncoderResult;

class MP4Encoder

public:
	MP4Encoder(void);
	~MP4Encoder(void);
	MP4EncoderResult MP4CreateFile(const char *sFileName,
		unsigned uRecordTime = DEFAULT_RECORD_TIME);
	MP4EncoderResult MP4AddH264Track(const uint8_t *sData, int nSize,
		int nWidth, int nHeight, int nFrameRate = 25);
	MP4EncoderResult MP4AddAACTrack(const uint8_t *sData, int nSize);
	MP4EncoderResult MP4WriteH264Data(uint8_t *sData, int nSize, uint64_t u64PTS);
	MP4EncoderResult MP4WriteAACData(const uint8_t *sData, int nSize,
		uint64_t u64PTS);
	void MP4ReleaseFile();
private:
	unsigned m_uSecond;
	MP4FileHandle m_hFile;
	bool m_bFirstAudio, m_bFirstVideo;
	MP4TrackId m_videoTrack, m_audioTrack;
	uint64_t m_u64AudioPTS, m_u64VideoPTS, m_u64FirstPTS, m_u64LastPTS;
;

实现部分:

// MP4Encoder.cpp
#pragma once
#include "MP4Encoder.h"
#pragma comment(lib, "../lib/libmp4v2.lib")

#define MIN_FRAME_SIZE 32
#define VIDEO_TIME_SCALE 90000
#define AUDIO_TIME_SCALE 8000
#define MOVIE_TIME_SCALE VIDEO_TIME_SCALE
#define PTS2TIME_SCALE(CurPTS, PrevPTS, timeScale) \\
	((MP4Duration)((CurPTS - PrevPTS) * 1.0 / (double)(1e+6) * timeScale))
#define INVALID_PTS 0xFFFFFFFFFFFFFFFF
/* Warning: Followings are magic data originally */
#define DEFAULT_VIDEO_TRACK_NUM 3
#define DEFAULT_VIDEO_PROFILE_LEVEL 1
#define DEFAULT_AUDIO_PROFILE_LEVEL 2

MP4Encoder::MP4Encoder(void)
	: m_hFile(MP4_INVALID_FILE_HANDLE)
	, m_bFirstVideo(true)
	, m_bFirstAudio(true)
	, m_uSecond(DEFAULT_RECORD_TIME)
	, m_videoTrack(MP4_INVALID_TRACK_ID)
	, m_audioTrack(MP4_INVALID_TRACK_ID)
	, m_u64VideoPTS(0)
	, m_u64AudioPTS(0)
	, m_u64FirstPTS(INVALID_PTS)
	, m_u64LastPTS(INVALID_PTS)




MP4Encoder::~MP4Encoder(void)



MP4EncoderResult MP4Encoder::MP4CreateFile(const char *sFileName,
	unsigned uRecordTime /* = DEFAULT_RECORD_TIME */)

	m_hFile = MP4Create(sFileName);
	if (m_hFile == MP4_INVALID_FILE_HANDLE)
		return MP4ENCODER_ERROR(MP4ENCODER_E_CREATE_FAIL);
	if (!MP4SetTimeScale(m_hFile, MOVIE_TIME_SCALE))
		return MP4ENCODER_ERROR(MP4ENCODER_E_CREATE_FAIL);
	m_uSecond = uRecordTime;
	return MP4ENCODER_ENONE;


MP4EncoderResult MP4Encoder::MP4AddH264Track(const uint8_t *sData, int nSize,
	int nWidth, int nHeight, int nFrameRate/* = 25 */)

	int sps, pps;
	for (sps = 0; sps < nSize;)
		if (sData[sps++] == 0x00 && sData[sps++] == 0x00 && sData[sps++] == 0x00
			&& sData[sps++] == 0x01)
			break;
	for (pps = sps; pps < nSize;)
		if (sData[pps++] == 0x00 && sData[pps++] == 0x00 && sData[pps++] == 0x00
			&& sData[pps++] == 0x01)
			break;
	if (sps >= nSize || pps >= nSize)
		return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_VIDEO_TRACK);

	m_videoTrack = MP4AddH264VideoTrack(m_hFile, VIDEO_TIME_SCALE,
		VIDEO_TIME_SCALE / nFrameRate, nWidth, nHeight,
		sData[sps + 1], sData[sps + 2], sData[sps + 3], DEFAULT_VIDEO_TRACK_NUM);
	if (MP4_INVALID_TRACK_ID == m_videoTrack)
		return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_VIDEO_TRACK);

	MP4SetVideoProfileLevel(m_hFile, DEFAULT_VIDEO_PROFILE_LEVEL);
	MP4AddH264SequenceParameterSet(m_hFile, m_videoTrack, sData + sps,
		pps - sps - 4);
	MP4AddH264PictureParameterSet(m_hFile, m_videoTrack, sData + pps,
		nSize - pps);

	return MP4ENCODER_ENONE;


MP4EncoderResult MP4Encoder::MP4AddAACTrack(const uint8_t *sData, int nSize)

	m_audioTrack = MP4AddAudioTrack(m_hFile, AUDIO_TIME_SCALE,
		/**
		 * In fact, this is not a magic number. A formula might be:
		 * SampleRate * ChannelNum * 2 / SampleFormat
		 * 8000 * 1 * 2 / 16 (字节对齐,这里是 AV_SAMPLE_FMT_S16)
		 */
		AUDIO_TIME_SCALE / 8, MP4_MPEG4_AUDIO_TYPE);
	if (MP4_INVALID_TRACK_ID == m_audioTrack)
		return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_AUDIO_TRACK);
	MP4SetAudioProfileLevel(m_hFile, DEFAULT_AUDIO_PROFILE_LEVEL);
	if (!MP4SetTrackESConfiguration(m_hFile, m_audioTrack, sData, nSize))
		return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_AUDIO_TRACK);

	return MP4ENCODER_ENONE;


MP4EncoderResult MP4Encoder::MP4WriteH264Data(uint8_t *sData, int nSize, uint64_t u64PTS)

	if (nSize < MIN_FRAME_SIZE)
		return MP4ENCODER_ENONE;
	bool result = false;
	sData[0] = (nSize - 4) >> 24;
	sData[1] = (nSize - 4) >> 16;
	sData[2] = (nSize - 4) >> 8;
	sData[3] = nSize - 4;
	if (m_bFirstVideo)
	
		if (m_u64FirstPTS > u64PTS)
			m_u64FirstPTS = u64PTS;
		m_u64VideoPTS = u64PTS;
		m_bFirstVideo = false;
	
	if ((sData[4] & 0x0F) == 5)
		result = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,
		PTS2TIME_SCALE(u64PTS, m_u64VideoPTS, VIDEO_TIME_SCALE));
	else
		result = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,
		PTS2TIME_SCALE(u64PTS, m_u64VideoPTS, VIDEO_TIME_SCALE), 0, false);
	if (!result)
		return MP4ENCODER_ERROR(MP4ENCODER_E_WRITE_VIDEO_DATA);
	m_u64LastPTS = m_u64VideoPTS = u64PTS;
	if (m_uSecond && (m_u64LastPTS - m_u64FirstPTS) / (1e+6) >= m_uSecond)
		return MP4ENCODER_ERROR(MP4ENCODER_WARN_RECORD_OVER);
	return MP4ENCODER_ENONE;


MP4EncoderResult MP4Encoder::MP4WriteAACData(const uint8_t *sData, int nSize,
	uint64_t u64PTS)

	if (nSize < MIN_FRAME_SIZE)
		return MP4ENCODER_ENONE;
	bool result = false;
	if (m_bFirstAudio)
	
		if (m_u64FirstPTS > u64PTS)
			m_u64FirstPTS = u64PTS;
		m_u64AudioPTS = u64PTS;
		m_bFirstAudio = false;
	
	result = MP4WriteSample(m_hFile, m_audioTrack, sData, nSize,
		PTS2TIME_SCALE(u64PTS, m_u64AudioPTS, AUDIO_TIME_SCALE));
	if (!result)
		return MP4ENCODER_ERROR(MP4ENCODER_E_WRITE_AUDIO_DATA);
	m_u64LastPTS = m_u64AudioPTS = u64PTS;
	if (m_uSecond && (m_u64LastPTS - m_u64FirstPTS) / (1e+6) >= m_uSecond)
		return MP4ENCODER_ERROR(MP4ENCODER_WARN_RECORD_OVER);
	return MP4ENCODER_ENONE;


void MP4Encoder::MP4ReleaseFile()

	if (m_hFile != MP4_INVALID_FILE_HANDLE)
	
		MP4Close(m_hFile);
		m_hFile = MP4_INVALID_FILE_HANDLE;
	


注意,这个是 PC 版本,Android 版本要修改 MP4WriteSample 中的 render!

但是问题又出现了,官方文档告知我们:如果是写视频数据,那么 MP4WriteSample 中的 render 是是不应该设置为默认值 0 的,因为有可能出现音视频不同步,结果我按照说明传递了参数,在手机上同步的更好,但是文件导出到电脑上播放时就会有音视频不同步的问题,这是不是官方的 bug 呢?期待有人去向官方反馈一下,因此现在我们就设置为默认值了,但是不保证以后是否要更改!

以上是关于H264和AAC的主要内容,如果未能解决你的问题,请参考以下文章

高清格式X264和H264有啥区别

H264和X264究竟有什么区别?

流媒体专家H264协议详解III H264文件解析C实现

具有多个 PPS 和 SPS 的 H264

流媒体专家H264协议详解II H264的分层结构与NALU介绍

h264和h265多维度区别