音視頻一些筆記
- 序列參數(shù)集SPS:作用于一系列連續(xù)的編碼圖像;
- 圖像參數(shù)集PPS:作用于編碼視頻序列中一個(gè)或多個(gè)獨(dú)立的圖像;
1.調(diào)用VTCompressionSessionCreate創(chuàng)建編碼session,然后調(diào)用VTSessionSetProperty設(shè)置參數(shù),最后調(diào)用VTCompressionSessionPrepareToEncodeFrames開始編碼;
int width = 480, height = 640;
OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &EncodingSession);
2.調(diào)用VTCompressionSessionEncodeFrame傳入需要編碼的視頻幀,如果返回失敗,調(diào)用VTCompressionSessionInvalidate銷毀session,然后釋放session;
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
// 幀時(shí)間,如果不設(shè)置會(huì)導(dǎo)致時(shí)間軸過(guò)長(zhǎng)。
CMTime presentationTimeStamp = CMTimeMake(frameID++, 1000);
VTEncodeInfoFlags flags;
OSStatus statusCode = VTCompressionSessionEncodeFrame(EncodingSession,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
NULL, NULL, &flags);
3、每一幀視頻編碼完成后會(huì)調(diào)用預(yù)先設(shè)置的編碼函數(shù)didCompressH264,如果是關(guān)鍵幀需要用CMSampleBufferGetFormatDescription獲取CMFormatDescriptionRef,然后用CMVideoFormatDescriptionGetH264ParameterSetAtIndex取得PPS和SPS;最后把每一幀的所有NALU數(shù)據(jù)前四個(gè)字節(jié)變成0x00 00 00 01之后再寫入文件。
OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 );
if (statusCode == noErr)
{
// Found pps
NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
if (encoder)
{
[encoder gotSpsPps:sps pps:pps];
}
}
4、調(diào)用VTCompressionSessionCompleteFrames完成編碼,然后銷毀session:VTCompressionSessionInvalidate,釋放session。
把原始碼流包裝成CMSampleBuffer
1.用CMBlockBuffer把NALUnit包裝起來(lái)
CMBlockBufferRef blockBuffer = NULL;
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
(void*)packetBuffer, packetSize,
kCFAllocatorNull,
NULL, 0, packetSize,
0, &blockBuffer);
2、把SPS和PPS包裝成CMVideoFormatDescription;
const uint8_t* parameterSetPointers[2] = {mSPS, mPPS};
const size_t parameterSetSizes[2] = {mSPSSize, mPPSSize};
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault 2, //param count parameterSetPointers, parameterSetSizes, 4, //nal start code size &mFormatDescription);
3.創(chuàng)建CMSampleBuffer;
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {packetSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault,blockBuffer,
mFormatDescription,
1, 0, NULL, 1, sampleSizeArray,
&sampleBuffer);
解碼并顯示
1、傳入CMSampleBuffer
VTDecodeFrameFlags flags = 0;
VTDecodeInfoFlags flagOut = 0;
// 默認(rèn)是同步操作。
// 調(diào)用didDecompress,返回后再回調(diào)
OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
sampleBuffer,
flags,
&outputPixelBuffer,
&flagOut);
2、回調(diào)didDecompress
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
*outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}
3、顯示解碼的結(jié)果
[self.mOpenGLView displayPixelBuffer:pixelBuffer];
當(dāng)遇到IDR幀時(shí),更合適的做法是通過(guò)
VTDecompressionSessionCanAcceptFormatDescription判斷原來(lái)的session是否能接受新的SPS和PPS,如果不能再新建session。
PCM通過(guò)抽樣、量化、編碼三個(gè)步驟將連續(xù)變化的模擬信號(hào)轉(zhuǎn)換為數(shù)字編碼。
- 抽樣:對(duì)模擬信號(hào)進(jìn)行周期性掃描,把時(shí)間上連續(xù)的信號(hào)變成時(shí)間上離散的信號(hào);
- 量化:用一組規(guī)定的電平,把瞬時(shí)抽樣值用最接近的電平值來(lái)表示,通常是用二進(jìn)制表示;
- 編碼:用一組二進(jìn)制碼組來(lái)表示每一個(gè)有固定電平的量化值;
iOS上把PCM音頻編碼成AAC音頻流
- 1、設(shè)置編碼器(codec),并開始錄制;
- 2、收集到PCM數(shù)據(jù),傳給編碼器;
- 3、編碼完成回調(diào)callback,寫入文件。
1、創(chuàng)建并配置AVCaptureSession
-
(void)startCapture { self.mCaptureSession = [[AVCaptureSession alloc] init]; mCaptureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); mEncodeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] lastObject]; self.mCaptureAudioDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:audioDevice error:nil]; if ([self.mCaptureSession canAddInput:self.mCaptureAudioDeviceInput]) { [self.mCaptureSession addInput:self.mCaptureAudioDeviceInput]; } self.mCaptureAudioOutput = [[AVCaptureAudioDataOutput alloc] init]; if ([self.mCaptureSession canAddOutput:self.mCaptureAudioOutput]) { [self.mCaptureSession addOutput:self.mCaptureAudioOutput]; } [self.mCaptureAudioOutput setSampleBufferDelegate:self queue:mCaptureQueue]; NSString *audioFile = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"abc.aac"]; [[NSFileManager defaultManager] removeItemAtPath:audioFile error:nil]; [[NSFileManager defaultManager] createFileAtPath:audioFile contents:nil attributes:nil]; audioFileHandle = [NSFileHandle fileHandleForWritingAtPath:audioFile]; [self.mCaptureSession startRunning]; }2、創(chuàng)建轉(zhuǎn)換器
AudioStreamBasicDescription是輸出流的結(jié)構(gòu)體描述,
配置好outAudioStreamBasicDescription后,
根據(jù)AudioClassDescription(編碼器),
調(diào)用AudioConverterNewSpecific創(chuàng)建轉(zhuǎn)換器。
(void) setupEncoderFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
AudioStreamBasicDescription inAudioStreamBasicDescription = *CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)CMSampleBufferGetFormatDescription(sampleBuffer));AudioClassDescription *description = [self getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC fromManufacturer:kAppleSoftwareAudioCodecManufacturer]; //軟編 OSStatus status = AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, description, &_audioConverter); // 創(chuàng)建轉(zhuǎn)換器 }
編解碼器(codec)指的是一個(gè)能夠?qū)σ粋€(gè)信號(hào)或者一個(gè)數(shù)據(jù)流進(jìn)行變換的設(shè)備或者程序。這里指的變換既包括將 信號(hào)或者數(shù)據(jù)流進(jìn)行編碼(通常是為了傳輸、存儲(chǔ)或者加密)或者提取得到一個(gè)編碼流的操作,也包括為了觀察或者處理從這個(gè)編碼流中恢復(fù)適合觀察或操作的形式的操作。編解碼器經(jīng)常用在視頻會(huì)議和流媒體等應(yīng)用中。
3、獲取到PCM數(shù)據(jù)并傳入編碼器
用CMSampleBufferGetDataBuffer獲取到CMSampleBufferRef里面的CMBlockBufferRef,
再通過(guò)CMBlockBufferGetDataPointer獲取到pcmBufferSize和pcmBuffer;
調(diào)用AudioConverterFillComplexBuffer傳入數(shù)據(jù),并在callBack函數(shù)調(diào)用填充buffer的方法。
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
status = AudioConverterFillComplexBuffer(_audioConverter, inInputDataProc, (__bridge void *)(self), &ioOutputDataPacketSize, &outAudioBufferList, outPacketDescription);
-
*/OSStatus inInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
AACEncoder *encoder = (__bridge AACEncoder *)(inUserData);
UInt32 requestedPackets = *ioNumberDataPackets;size_t copiedSamples = [encoder copyPCMSamplesIntoBuffer:ioData]; if (copiedSamples < requestedPackets) { //PCM 緩沖區(qū)還沒(méi)滿 *ioNumberDataPackets = 0; return -1; } *ioNumberDataPackets = 1; return noErr;}
/**
填充PCM到緩沖區(qū)
*/(size_t) copyPCMSamplesIntoBuffer:(AudioBufferList*)ioData {
size_t originalBufferSize = _pcmBufferSize;
if (!originalBufferSize) {
return 0;
}
ioData->mBuffers[0].mData = _pcmBuffer;
ioData->mBuffers[0].mDataByteSize = (int)_pcmBufferSize;
_pcmBuffer = NULL;
_pcmBufferSize = 0;
return originalBufferSize;
}
Audio Queue Services的播放步驟如下:
- 1,給buffer填充數(shù)據(jù),并把buffer放入就緒的buffer queue;
- 2,應(yīng)用通知隊(duì)列開始播放;
- 3、隊(duì)列播放第一個(gè)填充的buffer;
- 4、隊(duì)列返回已經(jīng)播放完畢的buffer,并開始播放下面一個(gè)填充好的buffer;
- 5、隊(duì)列調(diào)用之前設(shè)置的回調(diào)函數(shù),填充播放完畢的buffer;
- 6、回調(diào)函數(shù)中把buffer填充完畢,并放入buffer queue中。
/********FFMpeg從入門到精通********/
花上幾天學(xué)一個(gè)可以用幾十年的技術(shù)是何等高的學(xué)習(xí)“性價(jià)比”。
//生成的視頻結(jié)果是保留視頻的上半部分,同時(shí)上半部分會(huì)鏡像到視頻的下半部分,二者合成之后作為輸出視頻
?相同的Filter線性鏈之間用逗號(hào)分隔
?不同的Filter線性鏈之間用分號(hào)分隔
./ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
ffmpeg -i /Users/zly/Desktop/login_video.mp4 -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" /Users/zly/Desktop/login.mp4
ffmpeg--help查看到的help信息是ffmpeg命令的基礎(chǔ)信息
想獲得高級(jí)參數(shù)部分,那么可以通過(guò)使用ffmpeg--help long參數(shù)來(lái)查看
希望獲得全部的幫助信息,那么可以通過(guò)使用ffmpeg--help full參數(shù)來(lái)獲得。
如果要進(jìn)行格式化的顯示,這樣就需要用到ffprobe-print_format或者ffprobe-of參數(shù)來(lái)進(jìn)行相應(yīng)的格式輸出,而-print_format支持多種格式輸出,包括XML、INI、JSON、CSV、FLAT等。
ffprobe -of xml -show_streams input.flv
ffprobe-of ini-show_streams input.flv
ffprobe-of flat-show_streams input.flv
ffprobe-of json-show_packets input.flv
ffprobe-of csv-show_packets input.flv
ffplay常用的命令:
如果希望從視頻的第30秒開始播放,播放10秒鐘的文件,則可以使用如下命令:
ffplay -ss 30 -t 10 input.mp4
如果希望視頻播放時(shí)播放器的窗口顯示標(biāo)題為自定義標(biāo)題,則可以使用如下命令:
ffplay -window_title "Hello World, This is a sample" output.mp4
如果希望使用ffplay打開網(wǎng)絡(luò)直播流,則可以使用如下命令:
ffplay -window_title "播放測(cè)試" rtmp://up.v.test.com/live/stream
ffplay -window_title "播放測(cè)試" http://hdl.9158.com/live/b791155d46ff5e8940c795a41e43f67b.flv
vismv參數(shù)則是用來(lái)顯示圖像解碼時(shí)的運(yùn)動(dòng)向量信息的
ffplay -vismv pf output.mp4
ffplay -debug vis_mb_type -window_title "show vis_mb_type" -ss 20 -t 10 -autoexit output.mp4
FFmpeg轉(zhuǎn)封裝
moov音視頻數(shù)據(jù)的metadata信息
mdat media數(shù)據(jù)容器
因?yàn)镸P4的標(biāo)準(zhǔn)中描述的moov與mdat的存放位置前后并沒(méi)有進(jìn)行強(qiáng)制要求,所以有些時(shí)候moov這個(gè)Box在mdat的后面,有些時(shí)候moov被存放在mdat的前面。在互聯(lián)網(wǎng)的視頻點(diǎn)播中,如果希望MP4文件被快速打開,則需要將moov存放在mdat的前面;如果放在后面,則需要將MP4文件下載完成后才可以進(jìn)行播放。
常規(guī)的從文件轉(zhuǎn)換HLS直播時(shí),使用的參數(shù)如下:
./ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb output.m3u8
hls_time參數(shù)用于設(shè)置M3U8列表中切片的duration;例如使用如下命令行控制轉(zhuǎn)碼切片長(zhǎng)度為10秒鐘左右一片,該切片規(guī)則采用的方式是從關(guān)鍵幀處開始切片,所以時(shí)間并不是很均勻,如果先轉(zhuǎn)碼再進(jìn)行切片,則會(huì)比較規(guī)律:
./ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -hls_time 10 output.m3u8
hls_list_size參數(shù)用于設(shè)置M3U8列表中TS切片的個(gè)數(shù),通過(guò)hls_list_size可以控制M3U8列表中TS分片的個(gè)數(shù),命令行如下:
./ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -hls_list_size 3 output.m3u8
hls_base_url參數(shù)用于為M3U8列表中的文件路徑設(shè)置前置基本路徑參數(shù),因?yàn)樵贔Fmpeg中生成M3U8時(shí)寫入的TS切片路徑默認(rèn)為與M3U8生成的路徑相同,但是實(shí)際上TS所存儲(chǔ)的路徑既可以為本地絕對(duì)路徑,也可以為當(dāng)前相對(duì)路徑,還可以為網(wǎng)絡(luò)路徑
./ffmpeg -re -i input.mp4 -c copy -f hls -hls_base_url http://192.168.0.1/live/ -bsf:v h264_mp4toannexb output.m3u8
method參數(shù)用于設(shè)置HLS將M3U8及TS文件上傳至HTTP服務(wù)器,使用該功能的前提是需要有一臺(tái)HTTP服務(wù)器,支持上傳相關(guān)的方法,例如PUT、POST等,method方法的PUT方法可用于實(shí)現(xiàn)通過(guò)HTTP推流HLS的功能,首先需要配置一個(gè)支持上傳文件的HTTP服務(wù)器,本例使用Nginx來(lái)作為HLS直播的推流服務(wù)器,并且需要支持WebDAV功能,Nginx配置如下:
location / {
client_max_body_size 10M;
dav_access
group:rw all:rw;
dav_methods PUT DELETE MKCOL COPY MOVE;
root html/;
}
配置完成后啟動(dòng)Nginx即可。通過(guò)ffmpeg執(zhí)行HLS推流命令行如下:
./ffmpeg -i input.mp4 -c copy -f hls -hls_time 3 -hls_list_size 0 -method PUT -t 30 http://127.0.0.1/test/output_test.m3u8
FFmpeg抽取音視頻文件中的AAC音頻流:
FFmpeg提取MP4文件中的AAC音頻流的方法:
./ffmpeg -i input.mp4 -vn -acodec copy output.aac
FFmpeg抽取音視頻文件中的H.264視頻流
./ffmpeg -i input.mp4 -vcodec copy -an output.h264
FFmpeg抽取音視頻文件中的H.265數(shù)據(jù)
./ffmpeg -i input.mp4 -vcodec copy -an -bsf hevc_mp4toannexb -f hevc output.hevc
使用FFmpeg進(jìn)行封裝轉(zhuǎn)換時(shí)并不會(huì)占用大量的CPU資源,因?yàn)槭褂肍Fmpeg進(jìn)行封裝轉(zhuǎn)換時(shí)主要是以讀取音視頻數(shù)據(jù)、寫入音視頻數(shù)據(jù)為主,并不會(huì)涉及復(fù)雜的計(jì)算。如果使用FFmpeg進(jìn)行編碼轉(zhuǎn)換,則需要進(jìn)行大量的計(jì)算,從而將會(huì)占用大量的CPU資源。
/*************************iOS音視頻開發(fā)進(jìn)階*************************/
所謂采樣就是在時(shí)間軸上對(duì)信號(hào)進(jìn)行數(shù)字化。按比聲音最高頻率高2倍以上的頻率對(duì)聲音進(jìn)行采樣,(人耳能夠聽到的頻率范圍)是20Hz~20kHz,所以采樣頻率一般為44.1kHz,而所謂的44.1kHz就是代表1秒會(huì)采樣44100次.
量化是指在幅度軸上對(duì)信號(hào)進(jìn)行數(shù)字化,比如用16比特的二進(jìn)制信號(hào)來(lái)表示聲音的一個(gè)采樣,而16比特(一個(gè)short)所表示的范圍是[-32768,32767],共有65536個(gè)可能取值,因此最終模擬的音頻信號(hào)在幅度上也分為了65536層
既然每一個(gè)量化都是一個(gè)采樣,那么這么多的采樣該如何進(jìn)行存儲(chǔ)呢?這就涉及將要講解的第三個(gè)概念:編碼。所謂編碼,就是按照一定的格式記錄采樣和量化后的數(shù)字?jǐn)?shù)據(jù).
通常所說(shuō)的音頻的裸數(shù)據(jù)格式就是脈沖編碼調(diào)制(Pulse Code Modulation,PCM)數(shù)據(jù)。描述一段PCM數(shù)據(jù)一般需要以下幾個(gè)概念:量化格式(sampleFor-mat)、采樣率(sampleRate)、聲道數(shù)(channel)。以CD的音質(zhì)為例:量化格式(有的地方描述為位深度)為16比特(2字節(jié)),采樣率為44100,聲道數(shù)為2,這些信息就描述了CD的音質(zhì)。
比特率,即1秒時(shí)間內(nèi)的比特?cái)?shù)目,它用于衡量音頻數(shù)據(jù)單位時(shí)間內(nèi)的容量大小。44100 * 16 * 2 = 1378.125kbps
那么在1分鐘里,這類CD音質(zhì)的數(shù)據(jù)需要占據(jù)多大的存儲(chǔ)空間呢?計(jì)算如下:1378.125 * 60 / 8 / 1024 = 10.09MB
當(dāng)然,如果sampleFormat更加精確(比如用4字節(jié)來(lái)描述一個(gè)采樣),或者sampleRate更加密集(比如48kHz的采樣率),那么所占的存儲(chǔ)空間就會(huì)更大,同時(shí)能夠描述的聲音細(xì)節(jié)就會(huì)越精確。存儲(chǔ)的這段二進(jìn)制數(shù)據(jù)即表示將模擬信號(hào)轉(zhuǎn)換為數(shù)字信號(hào)了,以后就可以對(duì)這段二進(jìn)制數(shù)據(jù)進(jìn)行存儲(chǔ)、播放、復(fù)制,或者進(jìn)行其他任何操作。
麥克風(fēng)是如何采集聲音的:麥克風(fēng)里面有一層碳膜,非常薄而且十分敏感。聲音其實(shí)是一種縱波,會(huì)壓縮空氣也會(huì)壓縮這層碳膜,碳膜在受到擠壓時(shí)也會(huì)發(fā)出振動(dòng),在碳膜的下方就是一個(gè)電極,碳膜在振動(dòng)的時(shí)候會(huì)接觸電極,接觸時(shí)間的長(zhǎng)短和頻率與聲波的振動(dòng)幅度和頻率有關(guān),這樣就完成了聲音信號(hào)到電信號(hào)的轉(zhuǎn)換。之后再經(jīng)過(guò)放大電路處理,就可以實(shí)施后面的采樣量化處理了。
壓縮編碼的原理實(shí)際上是壓縮掉冗余信號(hào),冗余信號(hào)是指不能被人耳感知到的信號(hào),包含人耳聽覺(jué)范圍之外的音頻信號(hào)以及被掩蔽掉的音頻信號(hào)等。
各色光因其所形成的折射角不同而彼此分離,就像彩虹一樣,所以白光能夠分解成多種色彩的光。后來(lái)人們通過(guò)實(shí)驗(yàn)證明,紅綠藍(lán)三種色光無(wú)法被分解,故稱為三原色光。
假設(shè)一部手機(jī)屏幕的分辨率是1280×720,說(shuō)明水平方向有720個(gè)像素點(diǎn),垂直方向有1280個(gè)像素點(diǎn),所以整個(gè)手機(jī)屏幕就有1280×720個(gè)像素點(diǎn)(這也是分辨率的含義)。每個(gè)像素點(diǎn)都由三個(gè)子像素點(diǎn)組成(如圖1-7所示),這些密密麻麻的子像素點(diǎn)在顯微鏡下可以看得一清二楚。
RGB表示方式:取值范圍為0~255或者00~FF,8個(gè)比特表示一個(gè)子像素,32個(gè)比特表示一個(gè)像素,An-droid平臺(tái)上RGB_565的表示方法為16比特模式表示一個(gè)像素,R用5個(gè)比特來(lái)表示,G用6個(gè)比特來(lái)表示,B用5個(gè)比特來(lái)表示。對(duì)于一幅圖像,一般使用整數(shù)表示方法來(lái)進(jìn)行描述,比如計(jì)算一張1280×720的RGBA_8888圖像的大小,可采用如下方式:1280 * 720 * 4 = 3.516MB
YUV表示方式:YUV最常用的采樣格式是4:2:0,4:2:0并不意味著只有Y、Cb而沒(méi)有Cr分量。它指的是對(duì)每行掃描線來(lái)說(shuō),只有一種色度分量是以2:1的抽樣率來(lái)存儲(chǔ)的。相鄰的掃描行存儲(chǔ)著不同的色度分量,也就是說(shuō),如果某一行是4:2:0,那么其下一行就是4:0:2,再下一行是4:2:0,以此類推。
I幀:幀內(nèi)編碼幀(intra picture),I幀通常是每個(gè)GOP(MPEG所使用的一種視頻壓縮技術(shù))的第一個(gè)幀,經(jīng)過(guò)適度地壓縮,作為隨機(jī)訪問(wèn)的參考點(diǎn),可以當(dāng)成靜態(tài)圖像。
I幀自身可以通過(guò)視頻解壓算法解壓成一張單獨(dú)的完整視頻畫面,所以I幀去掉的是視頻幀在空間維度上的冗余信息。
P幀:前向預(yù)測(cè)編碼幀(predictive-frame),通過(guò)將圖像序列中前面已編碼幀的時(shí)間冗余信息充分去除來(lái)壓縮傳輸數(shù)據(jù)量的編碼圖像,也稱為預(yù)測(cè)幀。
P幀需要參考其前面的一個(gè)I幀或者P幀來(lái)解碼成一張完整的視頻畫面。
B幀:雙向預(yù)測(cè)內(nèi)插編碼幀(bi-directional interpolatedprediction frame),既考慮源圖像序列前面的已編碼幀,又顧及源圖像序列后面的已編碼幀之間的時(shí)間冗余信息,來(lái)壓縮傳輸數(shù)據(jù)量的編碼圖像,也稱為雙向預(yù)測(cè)幀。
B幀則需要參考其前一個(gè)I幀或者P幀及其后面的一個(gè)P幀來(lái)生成一張完整的視頻畫面,所以P幀與B幀去掉的是視頻幀在時(shí)間維度上的冗余信息。
IDR幀與I幀的理解:IDR的英文全稱instantaneous decoding refresh picture,因?yàn)镠264采用了多幀預(yù)測(cè),所以I幀之后的P幀有可能會(huì)參考I幀之前的幀,這就使得在隨機(jī)訪問(wèn)的時(shí)候不能以找到I幀作為參考條件,因?yàn)榧词拐业絀幀,I幀之后的幀還是有可能解析不出來(lái),而IDR幀就是一種特殊的I幀,即這一幀之后的所有參考幀只會(huì)參考到這個(gè)IDR幀,而不會(huì)再參考前面的幀。
對(duì)于視頻來(lái)說(shuō),AVFrame就是視頻的一幀圖像,這幀圖像什么時(shí)候顯示給用戶,取決于它的PTS(Presentation Time Stamp)。DTS(Decoding Time Stamp)是AVPacket里的一個(gè)成員,表示該壓縮包應(yīng)該在什么時(shí)候被解碼,如果視頻里各幀的編碼是按輸入順序(顯示順序)依次進(jìn)行的,那么解碼和顯示時(shí)間應(yīng)該是一致的,但是事實(shí)上,在大多數(shù)編解碼標(biāo)準(zhǔn)(如H.264或HEVC)中,編碼順序和輸入順序并不一致,于是才會(huì)需要PTS和DTS這兩種不同的時(shí)間戳。
GOP的概念:兩個(gè)I幀之間形成的一組圖片,就是GOP(Group Of Pic-ture)的概念。通常在為編碼器設(shè)置參數(shù)的時(shí)候,必須要設(shè)置gop_size的值,其代表的是兩個(gè)I幀之間的幀數(shù)目。解碼端必須從接收到的第一個(gè)I幀開始才可以正確解碼出原始圖像,否則會(huì)無(wú)法正確解碼。
增加C++支持
在單獨(dú)編寫一個(gè)C或C++的項(xiàng)目時(shí),如果該項(xiàng)目需要引用到第三方庫(kù),那么編譯階段需要配置參數(shù)“extra-cflags,-I”來(lái)指定引用頭文件的位置,鏈接階段需要配置參數(shù)“l(fā)d-flags,-L”來(lái)指定靜態(tài)庫(kù)的位置,并且使用-l來(lái)指定引用的是哪一個(gè)庫(kù)。
首先對(duì)應(yīng)于-I來(lái)指定頭文件的目錄,Xcode使用Search Paths來(lái)設(shè)置頭文件的搜索路徑,通常會(huì)用到Header Search Paths選項(xiàng)來(lái)指定頭文件的搜索路徑。其中預(yù)定義變量$(SRCROOT)和$(PROJECT_DIR)都是項(xiàng)目的根目錄,可以基于這兩個(gè)預(yù)定義變量再加上相對(duì)路徑來(lái)指定頭文件所在的具體位置;
在指定第三方庫(kù)時(shí),-L對(duì)應(yīng)到Xcode中就是other Link flags選項(xiàng),其中可以寫入需要鏈接的庫(kù)文件;在Xcode項(xiàng)目中直接添加一個(gè)靜態(tài)庫(kù)文件,Xcode會(huì)默認(rèn)在Build phases選項(xiàng)的Link Binary withLibrary里加入該靜態(tài)庫(kù),但是如果Xcode沒(méi)有自動(dòng)加入該靜態(tài)庫(kù)的話,就需要開發(fā)者手動(dòng)加一下,這里其實(shí)也是-L的一種表示方式。
使用本機(jī)器的編譯器,將源代碼編譯鏈接成為一個(gè)可以在本機(jī)器上運(yùn)行的程序。這就是正常的編譯過(guò)程,也稱為Native Compilation,中文譯作本機(jī)編譯。