版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
如何用FFmpeg編寫一個簡單器詳細步驟介紹()FFmpeg,器,編FFMPEG是一個很好的庫,可以用來創(chuàng)建應(yīng)用或者生成特定的工具。FFMPEG幾乎為你把所有的繁重工作都做了,比如、編碼、復(fù)用和解復(fù)用。這使得多應(yīng)用程序變得容易編寫。它是一個簡單的,用C編寫的,快速的并且能夠幾乎所有你能用到的格式,當然也包括編碼多種格式。外還有一個使用doxygen生成的文檔。這就是為什么當我決定研究FFMPEG來弄清楚音應(yīng)用程序是如何工作的過程中我決定把這個過程用文檔的形式記錄,,在FFMPEG工程中有一個示例的程序叫作ffplay。它是一個用C編寫的利用ffmpeg來實現(xiàn)完整的簡單器。這個指導(dǎo)將從原來MartinBohme寫的一個更新版本的指導(dǎo)開(我借鑒了一些基于FabriceBellard的ffplay,我將從那里開發(fā)一個可以使用的器。在每一個指導(dǎo)中,我將介紹一個或者兩個新的思想并且講解如何來實現(xiàn)它。每一個指導(dǎo)都會有一個C源文件你可以編譯并沿著這條思路來自己做源文件將向你展示一個真正的程序是如何運行如何來調(diào)用所有的部件,也將告訴你在這個指導(dǎo)中技術(shù)實 結(jié)束這個指導(dǎo)的時候有一個少于1000行代 ,,在寫器的過程中,使用SDL來輸出音頻和。SDL是一個優(yōu)秀的跨平臺的多,被用在MPEG、模擬器和很多中。你將需要SDL這篇指導(dǎo)適用于具有相當編程背景的人至少至少應(yīng)該懂得C并且有隊列和互斥太多,因為我將在這篇指導(dǎo)中介紹很多這樣的概念。更新:我修正了在指導(dǎo)7和8中的一些代碼錯誤,也添加-lavutil參數(shù)。歡迎 1:制作屏源代碼概文件有很多基本的組成部分。首先,文件本身被稱為Container,容器的類型決定了信息被存放在文件中的位置。AVIQuicktime子。接著,你有一組流,例如,你經(jīng)常有的是一個音頻流和一個流。(一個流只是一種想像出來的詞語,用來表示一連串的通過時間來串連的數(shù)據(jù)元素)。在流中的數(shù)據(jù)元素被稱為幀F(xiàn)rame。每個流是由不同的編來編碼生它的名字叫做CODEC。Divx和MP3就是編器的例子。接著從流中被讀出來應(yīng)用程序中操作的原始幀的數(shù)據(jù)。根據(jù)的目的,每個含了完整的幀或基本上來說,處理和音頻流是很容易的從.avi文件中打開從流中包到幀,在這個程序中使用ffmpeg來處理多種是相當容易的雖然很多程序可能在對幀進行操作的時候非常的復(fù)雜。因此在這篇指導(dǎo)中打開一個文件,讀取里面的流,而且對幀的操作將是把這個幀寫到一個PPM文件中。,打開文(<ffmpeg/avcodec.h>和<ffmpeg/avformat.h>來替#include<avcodec.h>#includeintmain(intargc,charg*argv[]){這里了所有的文件格式和編器的庫,所以它們將被自動的使用在被打av_register_all(在主函數(shù)main()中來調(diào)用它如果你喜歡,也可以只特定的格式和編器,但是通常你沒有必要這樣做。現(xiàn) 可以真正的打開文件AVFormatContext//Openif(av_open_input_file(&pFormatCtx,argv[1],NULL,0,NULL)!=0)return-1;//Couldn'topenfile通過第一個參數(shù)來獲得文件名這個函數(shù)文件的頭部并且把信息保存到AVFormatContext這個函數(shù)只是檢測了文件的頭部,所以接著需要檢查在文件中的流的信息//Retrievestreaminformationreturn-1;//Couldn'tfindstream這個函數(shù)為pFormatCtx->streams填充上正確的信息引進一個手工調(diào)試的//Dumpinformationaboutfileontostandarderrordump_format(pFormatCtx,0,argv[1],0);現(xiàn)在pFormatCtx->streamspFormatCtx->nb_streams所以讓先跳過它直到找到一個流。intAVCodecContext//Findthestreamfor(i=0;i<pFormatCtx->nb_streams; ){}if(Stream==-return-1;//Didn'tfinda//Getapointertothecodeccontextforthestream流中關(guān)于 器的信息就是 叫做"codeccontext"( 器上下文個指向他的指針。但是需要找到真正的編器并且打開它:AVCodec//Findthedecoderforthestreamif(pCodec==NULL){fprintf(stderr,"Unsupportedcodec!\n");return-1;//Codecnotfound}//Opencodecif(avcodec_opeodecCtx,pCodec)<0)return-1;//CouldnotopenCODEC_FLAG_TRUNCATEDpCodecCtx->flagshack在已經(jīng)保存了幀率的信息。time_base是一個結(jié)構(gòu)體,它里面有一個分子和分母(AVRational)使用分數(shù)的方式來表示幀率是因為很多編器使用非整數(shù)的幀率(NTSC29.97fps)。保存數(shù)現(xiàn)在需要找到一個地方來保存幀AVFrame//Allocateframe因為準備輸出保存24位RGB色的PPM文件,需把幀的格式從原來的都想把原始的幀轉(zhuǎn)換成一個特定的格式。讓先為轉(zhuǎn)換來申請一幀的內(nèi)//AllocateanAVFramestructurereturn-,即 仍然需要一個地方來放置原,。的數(shù)據(jù)使用avpicture_get_size來獲得需要的大小,然后手工申請。uint8_t*buffer;intnumBytes;//DeterminerequiredbuffersizeandallocatebuffernumBytes=avpicture_get_size(PIX_FMT_RGB24,pCodecCtx->width,buffer=(uint8_t*)av_malloc(numBytes*sizeof(uint8_t));證內(nèi)存地址是對齊的(42)。它并不能保護你不被內(nèi)存泄漏,重復(fù)或者其它malloc的問題所困擾?,F(xiàn)在使用avpicture_fill來把幀和新申請的內(nèi)存來結(jié)合。關(guān)于AVPictureAVPictureAVFrame――AVFrameAVPicture//Assignappropriatepartsofbuffertoimageplanesin//NotethatpFrameRGBisanAVFrame,butAVFrameisa//ofavpicture_fill((AVPicture*)pFrameRGB,buffer,pCodecCtx->width,pCodecCtx-最后,已經(jīng)準備好來從流中數(shù)據(jù)了。intframeFinished;AVPacketpacket;while(av_read_frame(pFormatCtx,&packet)>=0)//Isthisapacketfromthestream?if(packet.stream_index==Stream){//Decode nished,packet.data,//Didwegetaframe?if(frameFinished){//Converttheimagefromitsnativeformattimg_convert((AVPicture*)pFrameRGB,PIX_FMT_RGB24,(AVPicture*)pFrame,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height);//SavetheframetodiskSaveFrame(pFrameRGB,pCodecCtx-pCodecCtx->height,}}//Freethepacketthatwasallocatedbyav_read_frame}AVPacket結(jié)構(gòu)體中注意僅僅申請了一個包的結(jié)構(gòu)體――ffmpeg為申請了的數(shù)據(jù)的內(nèi)存并通過packet.data指針來指向它這些數(shù)據(jù)可以在后面通過av_free_packet()來。函數(shù)avcodec_decode_()把包轉(zhuǎn)換為幀。然而當一個包的時候,可能沒有得到需要的關(guān)于幀的信息。因此,當?shù)玫较乱粠臅r候,avcodec_decode_()為設(shè)置了幀結(jié)束標志frameFinished。最后,使用img_convert()函數(shù)來把幀從原始格式,(pCodecCtx->pix_fmt)RGBAVFrame結(jié)構(gòu)體的指針轉(zhuǎn)換為AVPicture結(jié)構(gòu)體的指針。最后把幀和高度寬度信息傳遞給的SaveFrame函數(shù)。,關(guān)于包PacketsPackets現(xiàn)在需要做的是讓SaveFrame函數(shù)能把RGB信息定稿到一個PPM格式的文件中。生成一個簡單的PPM格式文件,請相信,它是可以工作的。voidSaveFrame(AVFrame*pFrame,intwidth,intheight,intiFrame){FILE*pFile;charszFilename[32]; //Opensprintf(szFilename,"frame%d.ppm",iFrame);pFile=fopen(szFilename,"wb");//Writefprintf(pFile,"P6\n%d%d\n255\n",width,//Writepixeldatafor(y=0;y<height;y++)fwrite(pFrame->data[0]+y*pFrame->linesize[0],1,width*3,//Closefile}。做了一些標準的文件打開動作,然后寫入RGB數(shù)據(jù)一次向文件寫入一PPMRGBHTML#ff0000#ff0000....就表示了了個紅色的屏幕。(RGB?,F(xiàn)在,回顧的main()函數(shù)。一旦開始完流,需清理一//FreetheRGBimage//FreetheYUVframe//Closethecodec//Closethefilereturn你會注意到使用av_free來使用avcode_alloc_fram和上面的就是代碼!下面,使用Linux或者其它類似的平臺,你將運行g(shù)cc-otutorial01tutorial01.c-lavutil-lavformat-lavcodec-lz--ffmpeg,你可以去掉-lavutilgcc-otutorial01tutorial01.c-lavutil-lavformat-lavcodec-lz-大多數(shù)的圖像處理函數(shù)可以打開PPM文件。可以使用一些文件來進如何用FFmpeg編寫一個簡單器詳FFmpeg,器,編上來得到這個庫的源代碼或者如果有可能的話你可以直接開發(fā)包到你的操YUVYUV(YUV2Y1個U1V)。SDLYUVYUV4YUVYV12YUV420PYUVYV12是一樣的,除了UV4204:2:01/4。這是一種節(jié)省帶寬的好方式,因為人眼感覺不到這種變化。在名稱中的PY,U和V分量分別在不同的數(shù)組中。FFMPEGYUV420P,但是現(xiàn)在很多流的格式已經(jīng)是YUV420P的了或者可以被很容易的轉(zhuǎn)換成YUV420P格于是,現(xiàn)在計劃把指導(dǎo)1中的SaveFrame()函數(shù)替換掉,讓它直接輸出的幀到屏幕上去。但一開始需要先看一下如何使用SDL庫。首先SDL#include<SDL.h>#include<SDL_thread.h>if(SDL_Init(SDL_INIT_|SDL_INIT_AUDIO|SDL_INIT_TIMER)){ fprintf(stderr,"CouldnotinitializeSDL-%s\n",SDL_GetError()); SDL_Init()函數(shù)告訴了SDL庫,哪些特性要用到。當然現(xiàn)在需要在屏幕上的一個地方放上一些東西在SDL中顯示圖像的基本區(qū)域叫做surface。SDL_Surface*screen;screen=SDL_SetMode(pCodecCtx->width,pCodecCtx->height,0,0);if(!screen){ fprintf(stderr,"SDL:couldnotsetmode-exiting\n"); ――0示使用和當前一樣的深度。(OSX現(xiàn)在在屏幕上來創(chuàng)建一個YUV覆蓋以便于輸入上去: *bmp;bmp=SDL_CreateYUVOverlay(pCodecCtx->width, V12_OVERLAY,正如前面所說的,使用YV12來顯示圖像。,完成后的幀的。原來對RGB處理的方式,并且替換SaveFrame()為顯示到屏幕上的代碼。為了顯示到屏幕上先建立一個AVPicture結(jié)構(gòu)體并且設(shè)置其數(shù)據(jù)指針和行尺寸來為的YUV覆蓋服務(wù):,if(frameFinished){ pict.data[0]=bmp->pixels[0]; pict.data[1]=bmp- pict.data[2] pict.linesize[0]= pict.linesize[1]= pict.linesize[2]=bmp->pitches[1]; ConverttheimageintoYUVformatthatSDLuses *)pFrame,pCodecCtx->pix_fmt, ,首先鎖定這個覆蓋,因為要去改寫它。這是一個避免以后發(fā)生問題的好。正如前面所示的,這個AVPicture結(jié)構(gòu)體有一個數(shù)據(jù)指針指向一個有4個元素的指針數(shù)據(jù)。由于處理的是YUV420P,所以只需要3個通道即只要三組數(shù)據(jù)。其它的格式可能需要第四個指針來表示alpha通道或者其它像素pixel和程度pitch。(程度pitch是在SDL里用來表示指定行數(shù)據(jù)寬度的值)。所以現(xiàn)在做的是讓 的覆蓋中的pict.data中的三個指針有一個 可以直接從覆蓋中得到行尺寸信息像前面一樣使用img_convert來把格式轉(zhuǎn)換成PIX_FMT_YUV420P。,繪制圖但仍然需要告訴SDL如何來實際顯示給的數(shù)據(jù)也會傳遞一個表明SDL_Rect if(frameFinished) //ConverttheintoYUVformatthatSDLuses *)pFrame,pCodecCtx->pix_fmt, pCodecCtx- =0; rect.y=0; rect.w=pCodecCtx->width; rect.h= SDL_DisplayYUVOverlay(bmp,&rect); 現(xiàn)在的顯示出來了讓再花一點時間來看一下SDL的特性:它的事件驅(qū)動系統(tǒng)。SDL被設(shè)置成SDL序也可以產(chǎn)生事件并且傳遞給SDLSDL這相當有用這方面代碼可以在指導(dǎo)4中看到在這個程序中在理完包以后就立即輪詢事件。現(xiàn)在而言,處理SDL_QUIT事件以便于 switch(event.type){ SDLgcc-otutorial02tutorial02.c-lavutil-lavformat-lavcodec-lz-\`sdl-config--cflags--這里的sdl-config命令會打印出用于gcc編譯的包含正確SDL庫的適當參數(shù)。SDL,當運行這個程序的時候會發(fā)生什么呢?簡直跑瘋了!實際上只是以我們能從文件中幀的最快速度顯示了所有的的幀現(xiàn)在沒有任何代碼來計算出什么時候需要顯示的幀。最后(在指導(dǎo)5),花足夠情要處理:音頻!,指導(dǎo)3:聲現(xiàn) 要 聲音。SDL也 準備了輸出聲音的方法。函,特定的采樣率來進行錄制,采樣率表示以多快的速度來這段樣本流,它的表示方式為每秒多少次采樣。例如22050和44100的采樣率就是電臺和CD常用的采樣率。此外,大多音頻有不只一個通道來表示聲或者環(huán)繞。例如,如果采樣是聲,那么每次的采樣數(shù)就為2個。當從一個文件中等到數(shù)據(jù)的時候不知道得到多少個樣本,但是ffmpeg將不會給部分的樣本――這意味著它將不會把聲分割開來。,SDL聲音的方式是這樣的:你先設(shè)置聲音的選項:采樣率(在SDL的結(jié)構(gòu)體freqfrequency),聲音通道數(shù)和其它的參數(shù),然后設(shè)置一個回調(diào)函數(shù)和一些用戶數(shù)據(jù)userdata。當開始音頻的時候,SDL將不斷地調(diào)用這個回調(diào)函數(shù)并且要求它來向聲音緩沖填入一個特定的數(shù)量的字節(jié)。當把這些信息放到SDL_AudioSpec結(jié)構(gòu)體中后,調(diào)用函數(shù)SDL_OpenAudio()就會打開聲音設(shè)備并且給送回另外一個AudioSpec結(jié)構(gòu)體。這個結(jié)構(gòu)體是實際上用到的--因為不能保證得到所要求的。設(shè)置音//FindthestreamaudioStream=-for(i=0;i<pFormatCtx->nb_streams;i++)if(pFormatCtx->streams->codec->codec_type==CODEC_TYPEStream<0){}if(pFormatCtx->streams->codec->codec_type==CODEC_TYPE_AUDIO&&audioStream<0){}}if(Stream==-return-1;//Didn'tfindastreamreturn-從這里可以從描述流的AVCodecContext中得到想要的信息,就像得到流的信息一樣。AVCodecContextaCodecCtx=pFormatCtx->streams[audioStream]-包含在編上下文中的所有信息正是所需要的用來建立音頻的信息wanted_spec.freq=aCodecCtx->sample_rate;wanted_spec.format=AUDIO_S16SYS;wanted_spec.channels=aCodecCtx->channels;wanted_spec.silence=0;wanted_spec.samples=SDL_AUDIO_BUFFER_SIZE;wanted_spec.callback=audio_callback;wanted_spec.userdata=aCodecCtx;if(SDL_OpenAudio(&wanted_spec,&spec)<0){fprintf(stderr,"SDL_OpenAudio:%s\n",SDL_GetError());return-1;}讓瀏覽一下這些·freqformat“S16SYS1616,SYS這些格式是由avcodec_decode_audio2為給出來的輸入音頻的格式。·channels·silence0·samples這是當想要聲音的時候,想讓SDL給出來緩沖5128192;ffplay1024?!allback這個是的回調(diào)函數(shù)。后面將會詳細?!serdata這個是SDL供給回調(diào)函數(shù)運行的參數(shù)讓回調(diào)函數(shù)得到整個編的上下文;你將在后面知道原因。。最后,使用SDL_OpenAudio函數(shù)來打開聲音如果你還記得前面的指導(dǎo)仍然需要打開聲音編器本身這是很顯然的 aCodec=avcodec_find_decoder(aCodecCtx->codec_id);if(!aCodec){fprintf(stderr,"Unsupportedcodec!\n");return-1;}avcodec_open(aCodecCtx,隊嗯!現(xiàn)在已經(jīng)準備好從流中取出聲音信息。但是如何來處理這些信息方法為創(chuàng)建一個全局的結(jié)構(gòu)體變量以便于從文件中得到包有地方存放同時也保證SDL中回調(diào)函數(shù)audio_callback能從這個地方得到聲音數(shù)據(jù)。所以要做的是創(chuàng)建一個包的隊列queue。在ffmpeg中有一個叫AVPacketList的結(jié)構(gòu)體可以幫助,這個結(jié)構(gòu)體實際是一串包的鏈表。下面就是的隊列結(jié)構(gòu)體:typedefstructPacketQueue{AVPacketList*_pkt,*last_pkt;intnb_packets;intsize;SDL_mutex*mutex;SDL_cond*cond;}首先,應(yīng)當nb_packets是與size不一樣的--size表示從packet->size中得到的字節(jié)數(shù)。你會注意到有一個互斥量mutex和一個條condSDL理的。如果沒有正確的鎖定這個隊列,有可能把數(shù)據(jù)。來列,但是把這部分也來從而可以學(xué)習(xí)到SDL的函數(shù)。一開始先創(chuàng)建一個函數(shù)來初始化隊列voidpacket_queue_init(PacketQueue*q){memset(q,0,sizeof(PacketQueue));q->mutex=SDL_CreateMutex();q->cond=}接著再做一個函數(shù)來給隊列中填入東西intpacket_queue_put(PacketQueue*q,AVPacket*pkt){AVPacketList*pkt1;if(av_dup_packet(pkt)<0){return-1;}pkt1=av_malloc(sizeof(AVPacketList));if(!pkt1)return-1;pkt1->pkt=*pkt;pkt1->next=if(!q->last_pkt)q->_pkt=pkt1;q->last_pkt->next=pkt1;q->last_pkt=pkt1;q-q->size+=pkt1->pkt.size;return0;}函數(shù)SDL_LockMutex()鎖定隊列的互斥量以便于向隊列中添加?xùn)|西然后數(shù)SDL_CondSignal()通過的條件變量為一個接收函(如果它在等待出一個信號來告訴它現(xiàn)在已經(jīng)有數(shù)據(jù)了接著就會互斥量并讓隊列可以。下面是相應(yīng)的接收函數(shù)。注意函數(shù)SDL_CondWait()是如何按照的要求讓block(例如一直等到隊列中有數(shù)據(jù))。intquit=staticintpacket_queue_get(PacketQueue*q,AVPacket*pkt,intblock){AVPacketList*pkt1;intfor(;;){if(quit)ret=-}pkt1=q->_pkt;if(pkt1){q->_pkt=pkt1->next;if(!q->_pkt)q->last_pkt=NULL;q->nb_packets--q->size-=pkt1-*pkt=pkt1->pkt;ret=1;}elseif(!block){ret=0;}elseSDL_CondWait(q->cond,q-}}returnret;}。正如你所看到的已經(jīng)用一個無限循環(huán)包裝了這個函數(shù)以便于想用阻塞的方式來得到數(shù)據(jù)通過使用SDL中的函數(shù)SDL_CondWait()來避免無限循CondWaitSDL_CondSignal()函數(shù)(或者來陷入了的互斥體中--如果保持著這個鎖的函數(shù)將永遠無法把數(shù)據(jù)放入到隊列中去!但是,SDL_CondWait()函數(shù)也為做了。意外情將會注意到有一個全局變量quit,用它來保證還沒有設(shè)置程序退出的信號(SDLTERM)。否則,這個線程將不停地運行直到使用kill-9來結(jié)束程序。FFMPEG同樣也提供了一個函數(shù)來進行回調(diào)并檢查是否需要退出一些被阻塞的函數(shù):這個函數(shù)就是intdecode_interrupt_cb(void){returnquit;}}}main()
switch(event.type){caseSDL_QUIT:quit=當然,這僅僅是用來給ffmpeg中的阻塞情況使用的,而不是SDL中的。還quit1。為隊列提供剩下的唯一需要為隊列所做的事就是提供包了PacketQueueaudioq;main(){avcodec_open(aCodecCtx,據(jù),它會靜音。while(av_read_frame(pFormatCtx,&packet)>=0)//Isthisapacketfromthestream?//Decode}}elseif(packet.stream_index==audioStream){packet_queue_put(&audioq,&packet);}else{注意:沒有在把包放到隊列里的時候它,在后來它。現(xiàn)在,讓最后讓聲音回調(diào)函數(shù)audio_callback來從隊列中取出包?;卣{(diào)函voidcallback(void*userdata,Uint8*stream,intlen),這里的userdata就是給到SDL的指針,stream是要把聲音數(shù)據(jù)寫入的緩沖區(qū)指針,lenvoidaudio_callback(void*userdata,Uint8*stream,intlen)AVCodecContext*aCodecCtx=(AVCodecContext*)userdata;intlen1,audio_size;staticuint8_taudio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE*3)/2];staticunsignedintaudio_buf_size=0;staticunsignedintaudio_buf_index=while(len>0)if(audio_buf_index>=audio_buf_size)audio_size=audio_decode_frame(aCodecCtx,if(audio_size<0)audio_buf_size=memset(audio_buf,0,}elseaudio_buf_size=}audio_buf_index=
}len1=audio_buf_size-audio_buf_index;if(len1>len)len1=memcpy(stream,(uint8_t*)audio_buf+audio_buf_index,len1);len-=len1;stream+=len1;audio_buf_index+=len1;}這基本上是一個簡單的從另外一個要寫的audio_decode_frame()函數(shù)字節(jié)并且在沒有足夠的數(shù)據(jù)的時候會獲取的數(shù)據(jù)或者當有多余數(shù)據(jù)的時候保存下來為后面使用。這個audio_buf的大小為1.5倍幀的大ffmpeg最后音讓看一下器的真正部分intaudio_decode_frame(AVCodecContext*aCodecCtx,uint8_tintbuf_size)staticAVPacketstaticuint8_t*audio_pkt_data=NULL;staticintaudio_pkt_size=0;intlen1,data_size;for(;;){while(audio_pkt_size>0)data_size=audio_pkt_data,if(len1<0)audio_pkt_size=0;}audio_pkt_data+=len1;audio_pkt_size-=len1;if(data_size<=0){}return}if(quit){return-}if(packet_queue_get(&audioq,&pkt,1)<0)return-}audio_pkt_data=pkt.data;audio_pkt_size=}}整個過程實際上從函數(shù)的尾部開始,在這里調(diào)用了packet_queue_get()函數(shù)。從隊列中取出包,并且保存它的信息。然后,一旦有了可以使用avcodec_decode_audio2(avcodec_decode_()一樣唯一的區(qū)別是它的一個包里可能有不止一個聲幀,所以你可能要調(diào)用很多次來出包中所有的數(shù)據(jù)。同時也要記住進行指audio_bufSDL8ffmpeg16len1data_size表示使用的數(shù)據(jù)的在包中的大小data_size表示實際返回的原始聲音數(shù)的大小。,當?shù)玫揭恍?shù)據(jù)的時候立刻返回來看一下是否仍然需要從隊列中得到更加多的數(shù)據(jù)或者已經(jīng)完成了。如果仍然有更加多的數(shù)據(jù)要處理把它保存到下一次。如果完成了一個包的處理,最后要它。,就是這樣。利用主的隊列循環(huán)從文件得到音頻并送到隊列中,然后被audio_callback函數(shù)從隊列中并處理,最后把數(shù)據(jù)送給SDL,于是SDL就相當于的聲卡。讓繼續(xù)并且編譯:gcc-otutorial03tutorial03.c-lavutil-lavformat-lavcodec-lz-lm`sdl-config--cflags--!啊哈雖然還是像原來那樣快,但是聲音可以正常了。這是為什么呢?因為聲音信息中的采樣率--雖然把聲音數(shù)據(jù)盡可能快的填充到聲卡緩沖中,但是聲音設(shè)備卻會按照原來指定的采樣率來進行。!幾乎已經(jīng)準備好來開始同步音頻和了但是首先需要的是一點程序得程序更加更加易于控制和模塊化。在開始同步音之前,需要如何用FFmpeg編寫一個簡單器詳FFmpeg,器,編}else}}//Isthisapacketfromthestream?if(packet->stream_index==is->Stream){packet_queue_put(&is->q,}elseif(packet->stream_index==is->audioStream){packet_queue_put(&is->audioq,packet);}else}}這里沒有什么新東西除了給音頻和隊列限定了一個最大值并且添pbByteIOContexturl_ferror來檢測結(jié)構(gòu)體并發(fā)現(xiàn)是否有些文件錯誤。,在循環(huán)以后的代碼是用等待其余的程序結(jié)束和提示已經(jīng)結(jié)束的。這些代碼是有益的,因為它指示出了如何驅(qū)動事件--后面顯示影像。,while(!is->quit){}SDL_Eventevent.type=FF_QUIT_EVENT;event.user.data1=is;}return使用SDL常量SDL_USEREVENT來從用戶事件中得到值第一個用戶事件的值應(yīng)當是SDL_USEREVENT,下一個是SDL_USEREVENT+1并且依此類推。在的遞用戶數(shù)據(jù),在這里傳遞的是大結(jié)構(gòu)體的指針。最后調(diào)用 SDL_QUIT_EVENT部分一樣。在自己的事件隊列中詳細,現(xiàn)在只是確保正確放入了FF_QUIT_EVENT事件,在后面捕捉到它并且設(shè)置quit。得到幀:,當準備好器后開始線程。這個線程從隊列中包,把它成幀,然后調(diào)用queue_picture函數(shù)把處理好的幀放入到隊列,int_thread(void*arg){State*is=(State*)arg;AVPacketpkt1,*packet=&pkt1;intlen1,frameFinished;AVFrame*pFrame;pFrame=avcodec_alloc_frame();for(;;){if(packet_queue_get(&is->q,packet,1)<0)//meanswequitgettingpackets}//Decodelen1=avcodec_decode_(is->_st->codec,pFrame,packet->data,packet-//Didwegetaframe?if(frameFinished){if(queue_picture(is,pFrame)<0){}}}return0;}在這里的很多函數(shù)應(yīng)該很熟悉吧。把avcodec_decode_函數(shù)移到了這里,替換了一些參數(shù),例如:把AVStream保存在自己的大結(jié)構(gòu)體中,所以可以從那里得到編器的信息僅僅是不斷的從隊列中取包 把幀隊列讓看一下保存后的幀pFrame到圖像隊列中去的函數(shù)。因為的圖像隊列是SDL的覆蓋的集(基本上不用讓顯示函數(shù)再做計算了)需要把幀轉(zhuǎn)換成相應(yīng)的格式保存到圖像隊列中的數(shù)據(jù)是自己做的一個結(jié)構(gòu)typedefstructPicture{SDL_Overlay*bmp;intwidth,height;intallocated;}的大結(jié)構(gòu)體有一個可以保存這些緩沖區(qū)。然而,需要自己來申請SDL_Overlay(注意:allocated標志會指明是否已經(jīng)做了這個申請的動作,。,為了使用這個隊列有兩個指針--寫入指針和指針也要保證一定數(shù)量的實際數(shù)據(jù)在緩沖中。要寫入到隊列中先要等待緩沖清空以便于有位置來保存的Picture。然后 檢查看是否已經(jīng)申請到了一個 也要重新申請緩現(xiàn)在還不太清楚原因;我相信是為了避免在其它線程中調(diào)用SDL覆蓋函數(shù)的原,。,intqueue_picture(State*is,AVFrame*pFrame)intAVPicturewhile(is->pictq_size>=!is->quit)SDL_CondWait(is->pictq_cond,is-}SDL_UnlockMutex(is-return-//windexissetto0vp=&is->pictq[is-if(!vp->bmpvp->width!=is->_st->codec->width||vp->height!=is->_st->codec->height){SDL_Eventevent;vp->allocated=0;event.type=FF_ALLOC_EVENT;event.user.data1=is;while(!vp->allocated&&!is->quit){SDL_CondWait(is->pictq_cond,is-}if(is->quit){return-}}這里的事件機制與前面想要退出的時候看到的一樣。已經(jīng)定義了事件FF_ALLOC_EVENT作為SDL_USEREVENT。把事件發(fā)到事件隊列中然后等待申讓來看一看如何來修改事件循環(huán)for(;;)switch(event.type){caseFF_ALLOC_EVENT:記住event.user.data1是的大結(jié)構(gòu)體。就這么簡單。讓看一alloc_picture()voidalloc_picture(void*userdata)State*is=(State*)userdata;Picture*vp;vp=&is->pictq[is->pictq_windex];if(vp->bmp){//wealreadyhaveonemakeanother,bigger/smaller}//AllocateaplacetoputourYUVimageonthatvp->bmp=SDL_CreateYUVOverlay(is->_st->codec-is->_st->codec->height,vp->width=is->_st->codec->width;vp->height=is->_st->codec->height;vp->allocated=1;}你可以看到把SDL_CreateYUVOverlay函數(shù)從主循環(huán)中移到了這里。這段代中因為需要保存的的大小沒有因為某些原因而改變。,好幾乎已經(jīng)全部解決并且可以申請到Y(jié)UV覆蓋和準備好接收圖像。讓,intqueue_picture(State*is,AVFrame*pFrame)if(vp->bmp){dst_pix_fmt=PIX_FMT_YUV420P;pict.data[0]=vp->bmp->pixels[0];pict.data[1]=vp->bmp->pixels[2];pict.data[2]=vp->bmp-pict.linesize[0]=vp->bmp->pitches[0];pict.linesize[1]=vp->bmp->pitches[2];pict.linesize[2]=vp->bmp-//ConverttheimageintoYUVformatthatSDLusesimg_convert(&pict,dst_pix_fmt,(AVPicture*)pFrame,is->_st->codec-is->_st->codec->width,is->_st->codec->height);if(++is->pictq_windex==_PICTURE_QUEUE_SIZE){is->pictq_windex=0;}}return}這部分代碼和前面用到的一樣主要是簡單的用的幀來填充YUV覆蓋最1is->pictq_size需要鎖定它。這里做的是增加寫指針(在必要的時候采用輪轉(zhuǎn)的方式),然后鎖定隊列并且增加尺寸現(xiàn)在的讀者函數(shù)將會知道隊列中有了的信息,當隊列滿的時候,的寫入函數(shù)也會知道。顯示這就是的線程現(xiàn)在看過了幾乎所有的線程除了一個--記得調(diào)用schedule_refresh()函數(shù)嗎?讓看一下實際中是如何做的:staticvoidschedule_refresh(State*is,intdelay){SDL_AddTimer(delay,sdl_refresh_timer_cb,is);}SDL_AddTimer()SDL(特定的毫秒)執(zhí)行用戶定義的回調(diào)函數(shù)(可以帶一些參數(shù)userdata)的簡單函數(shù)。用這個函數(shù)來定時刷--每次調(diào)用這個函數(shù)的時候它將設(shè)置一個定時器來觸發(fā)定件來把一幀從圖像隊列中顯示到屏幕上。但是, 先觸發(fā)那個事件staticUint32sdl_refresh_timer_cb(Uint32interval,void*opaque){SDL_Eventevent;event.type=FF_REFRESH_EVENT;event.user.data1=opaque;return}這里向隊列中寫入了一個現(xiàn)在很熟悉的事件。FF_REFRESH_EVENTSDL_USEREVENT+10SDL現(xiàn)在產(chǎn)生了一個FF_REFRESH_EVENT事件,需要在事件循環(huán)中處理它for(;;)switch(event.type){case于是就運行到了這個函數(shù),在這個函數(shù)中會把數(shù)據(jù)從圖像隊列中取出void_refresh_timer(void*userdata)State*is=(State*)userdata;Picture*vp;if(is->_st)if(is->pictq_size==0){schedule_refresh(is,1);}elsevp=&is->pictq[is-schedule_refresh(is,if(++is->pictq_rindex==_PICTURE_QUEUE_SIZE){is->pictq_rindex=0;}}}else{schedule_refresh(is,100);}}:。為下一幀設(shè)置定時器,調(diào)用_display函數(shù)來真正顯示圖像到屏幕上,然后把隊列讀索引值加1,并且把隊列的尺寸size減1。你可能會注意到在這個函數(shù)中并沒有真正對vp做一些實際的動作,原因是這樣的在后面處理在后面同步音頻和的時候用它來 這個注釋信“timinghere”那里 什么時候顯示下一幀,然后把相應(yīng)的值寫入到schedule_refresh()函數(shù)中。現(xiàn)在只是隨便寫入一個值80。從技術(shù)上來講,你可以猜測并驗證這個值,并且為每個重新編譯程序,但是:1)過一段時間它會漂移;2)這種方式是很笨的。在后面來它。:。幾乎做完了僅僅剩了最后一件事顯示下面就是void_display(State*is){SDL_Rectrect;Picture*vp;AVPicturepict;floataspect_ratio;intw,h,x,y;intvp=&is->pictq[is->pictq_rindex];if(vp->bmp){if(is->_st->codec->sample_aspect_ratio.num==0){aspect_ratio=0;}elseaspect_ratioav_q2d(is->_st->codec->sample_aspect_ratio)is->_st->codec->width/is->_st->codec-}if(aspect_ratio<=0.0)aspect_ratio=(float)is->_st->codec->width(float)is->_st->codec-}h=screen-w=((int)rint(h*aspect_ratio))&-3;if(w>screen->w){w=screen-h=((int)rint(w/aspect_ratio))&-}x=(screen->w-w)/2;y=(screen->h-h)/rect.x=x;rect.y=y;rect.w=w;rect.h=SDL_DisplayYUVOverlay(vp->bmp,}} 的屏幕可以是任意尺(設(shè)置為640x480并且用戶可以自己來改變 需要動態(tài)計算出顯示的圖像的矩形大小所以一開始需要計算出的比aspectratio,表示方式為寬度除以高度。某些編器會有奇數(shù)采樣比只是簡單表示了一個像素或者一個采樣的寬度除以高度的比例。因為寬度和高度在的編器中是用像素為單位的,所以實際的縱橫比與比乘以樣本比相同。某些編器會顯示比為0,這表示每個像素的比為1x1。然后把縮放到適合屏幕的盡可能大的尺寸。這里的&-3表示與-3做與運算,實際上是讓它們4字節(jié)對齊。然后把移到中心位置,接著調(diào)用SDL_DisplayYUVOverlay()函數(shù)。結(jié)果是什么?做完了嗎?嗯仍然要重新改寫聲音部分的代碼來使用新例代碼。最后要做的是改變ffmpeg提供的默認退出回調(diào)函數(shù)為的退出回調(diào)函數(shù)。State*global__state;intdecode_interrupt_cb(void)return(global__state&&global__state-}在主函數(shù)中為大結(jié)構(gòu)體設(shè)置了global__state。這就是了!讓編譯它:gcc-otutorial04tutorial04.c-lavutil-lavformat-lavcodec-lz-lm`sdl-config--cflags--請享受一下沒有經(jīng)過同步的下次編譯一個可以最終工作的,前面整個的一段時間有了一個幾乎無用的 頻,也能音頻,但是它還不能被稱為一部 ,PTS幸運的是音頻和流都有一些關(guān)于以多快速度和什么時間來它們的信息在里面。音頻流有采樣,流有每秒的幀率。然而,如果只是簡單的通過數(shù)幀和乘以幀率的方式來同步,那么就很有可能會失去同步。于是作為一為了這兩個參數(shù),你需要了解存放的方式。像MPEG等格式,使用被叫做B幀(Bbidrectional)IP(I示關(guān)鍵幀,P表示幀)。I幀包含了某個特定的完整圖像。P幀依賴于前面的IPB幀與P它是依賴于前面和后面的幀的信息的。這也就解釋了為什么可能在調(diào)用avcodec_decode_以后會得不到一幀圖像。所以對于一個,幀是這樣來顯示的:IBBP。現(xiàn)在需要在顯示B幀之前知道P幀中的信息。因此,幀可能會按照這樣的方式來:IPBB。這就是為什么會有一個時間戳和一個顯示時間戳的原因。時間戳告訴下,的流可以是這樣的:PTS:142DTS:123Stream:IPBPTSDTSB:當調(diào)用av_read_frame()得到一個包的時候,PTS和DTS的信息也會保存在包中。但是真正想要的PTS是剛剛出來的原始幀的PTS,這樣我們才能知道什么時候來顯示它。然而,從avcodec_decode_()函數(shù)中AVFrame,PTS(注意:AVFrame并沒有包含時間戳信息但當?shù)鹊綆臅r候并不是想要的樣子然而,ffmpeg重新排序包以便于被avcodec_decode_()函數(shù)處理的包的DTS可以總是與其返回的PTS相同。但是,另外的一個警告是也并不是總能得到這個信息。:。。排序 。。以通過函數(shù)avcodec_decode_()來計算出哪個包是一幀的第一個包。怎樣實現(xiàn)呢?任何時候當一個包開始一幀的時候,avcodec_decode_()將調(diào)用一個函數(shù)來為一幀申請一個緩沖。當然,ffmpeg允許重新定義那個分配內(nèi)存的函數(shù)。所以制作了一個新的函數(shù)來保存一個包的時間戳。,。當然,盡管那樣可能還是得不到一個正確的時間戳在后面處理這,。同,里有個主意:當顯示了一幀以后計算出下一幀顯示的時間。然后簡單的設(shè)置一個新的定時器來。你可能會想,檢查下一幀的PTS值而不是,首先,要知道下一個PTS是什么。現(xiàn)在能添加速率到的PTS中--致程序顯示下一幀太快了。所以需要計算它們。第二,正如程序現(xiàn)在這樣,和音頻很歡快,一點也不受同步的影響。如果一切都工作得很好的話,不必擔心。但是,你的電腦并不是最好的,很多文件也不是完好的。所以,有三種選擇:同步音頻到,同步到音頻,或者都同步到外部時鐘(例如你的電腦時鐘)。從現(xiàn)在開始,同步到音頻。寫代碼:獲得幀的時間現(xiàn)在讓到代碼中來做這些事情。需要為的大結(jié)構(gòu)體添加一些成們得到了線程輸出到隊列中的包。這里 avcodec_decode_函數(shù)中得到幀的時間戳。 的第式是從上次處理的包中得到DTS,這是很容易的:doublepts;for(;;)if(packet_queue_get(&is->q,packet,1)<0)//meanswequitgettingpackets}pts=//Decodelen1=avcodec_decode_(is->_st-packet->data,packet->size);if(packet->dts!=AV_NOPTS_VALUE){
pts=packet-}elsepts=}pts*=av_q2d(is->_st-如果得不到PTS就把它設(shè)置為0,。好,那是很容易的。但是所說的如果包的DTS不能幫到需要使用這一幀的第一個包的PTS通過讓ffmpeg使用自己的申請幀程序來實,。intget_buffer(structAVCodecContext*c,AVFramevoidrelease_buffer(structAVCodecContext*c,AVFrame。,申請函數(shù)沒有告訴關(guān)于包的任何事情所以要自己每次在得到一個包的時候把PTS保存到一個全局變量中去自己以讀到它。然后把值保存到AVFrame結(jié)構(gòu)體難理解的變量中去。所以一開始,這就是的函數(shù):。,uint64_tglobal__pkt_pts=intour_get_buffer(structAVCodecContext*c,AVFrame*pic){intret=avcodec_default_get_buffer(c,pic);uint64_t*pts=*pts=global__pkt_pts;pic->opaque=pts;return}voidour_release_buffer(structAVCodecContext*c,AVFrame*pic){if(pic)av_freep(&pic->opaque);avcodec_default_release_buffer(c,pic);}avcodec_default_get_bufferavcodec_default_release_bufferffmpegav_freep但把內(nèi)存而且把指針設(shè)置為NULL?,F(xiàn)在到了流打開的函數(shù)( 訴ffmpeg如何去做:codecCtx->get_buffer=codecCtx->release_buffer=現(xiàn)在需添加代碼來保存PTS到全局變量中,然后在需要的時候來使用它。for(;;)if(packet_queue_get(&is->q,packet,1)<0)//meanswequitgettingpackets}pts=//SaveglobalptstobestoredinpFrameincallglobal__pkt_pts=packet->pts;//Decodelen1=avcodec_decode_(is->_st->codec,pFrame,packet->data,packet->size);if(packet->dts==AV_NOPTS_VALUE&&pFrame->opaque&&*(uint64_t*)pFrame->opaque!=AV_NOPTS_VALUE){pts=*(uint64_t*)pFrame-}elseif(packet->dts!=AV_NOPTS_VALUE){pts=packet->dts;}elsepts=}pts*=av_q2d(is->_st-型來保存的。這個值是一個時間戳相當于時間的度量,用來以流的time_base幀應(yīng)該排在第42個幀的位置如果每秒有24幀(這里并不完全正確)。time_base1/framerate(對于固定幀率來說),所以得到了以秒為單位的PTS,需要time_base。寫代碼:使用PTS來同現(xiàn)在得到了PTS。要注意前面到的兩個同步問題。定義一個得不到PTS的情況。同時要知道下一幀的時間以便于正確設(shè)置刷新速率。可以使用的反映當前已經(jīng)時間的時鐘_clock來完成這個功能。把這些值添加到大結(jié)構(gòu)體中。typedefstructState _clock;下面的是函數(shù)synchronize_,它可以很好的自我注釋doublesynchronize_(State*is,AVFrame*src_frame,double{doubleframe_delay;if(pts!=0){is->_clock=}elsepts=is->}frame_delay=av_q2d(is->_st->codec-frame_delay+=src_frame->repeat_pict*(frame_delay*0.5);is->_clock+=frame_delay;return}你也會注意到也計算了重復(fù)的幀現(xiàn)在讓得到正確的PTS并且使用queue_picture來隊列化幀添加一個新的pts://Didwegetaframe?if(frameFinished){pts=synchronize_(is,pFrame,pts);if(queue_picture(is,pFrame,pts)<0){}},對于queue_picture來說唯一改變的事情就是把時間戳值pts保存到Picture結(jié)構(gòu)體中需添加一個時間戳變量到結(jié)構(gòu)體中并且添,typedefstructPicturedouble}intqueue_picture(State*is,AVFrame*pFrame,doublepts)...stuff...if(vp->bmp)...convertpicture...vp->pts=pts;...alertqueue}現(xiàn)在的圖像隊列中的所有圖像都有了正確的時間戳值所以讓看一下視頻刷新函數(shù)。你會記得上次用80ms的刷新時間來它。那么,現(xiàn)在將會算出實際的值。的策略是通過簡單計算前一幀和現(xiàn)在這一幀的時間戳來出下一個時間戳的時間。同時,需要同步到音頻。設(shè)置一個音頻時間audioclock;一個值記錄了正在的音頻的位置。就像從任意的mp3器中讀出來的數(shù)字一樣既然把同步到音頻線程使用這個值來算在后面來實現(xiàn)這些代碼現(xiàn)在假設(shè)已經(jīng)有一個可以給音頻時間的函數(shù)get_audio_clock。一旦有了這個值,在音頻和失去同的方式來解決作為一種替代段 會調(diào)整下次刷新的值如果時間戳,,于音頻時間加倍計算延遲。如果時間戳太領(lǐng)先于音頻時間盡可能快的刷新。既然有了調(diào)整過的時間和延遲,把它和通過frame_timerframe_timer影中所有的延時。換句話說,這個frame_timer就是指什么時候來顯示下一幀簡單的添加新的幀定時器延時把它和電腦的系統(tǒng)時間進行比較,然后使用那個值來調(diào)度下一次刷新。這可能有點難以理解,所以請認真研究代,,void_refresh_timer(void*userdata)State*is=(State*)userdata;Picture*vp;doubleactual_delay,delay,sync_threshold,ref_clock,if(is->_st)if(is->pictq_size==0){schedule_refresh(is,1);}elsevp=&is->pictq[is-delay=vp->pts-is->frame_last_pts;if(delay<=0||delay>=1.0){delay=is-}is->frame_last_delay=delay;is->frame_last_pts=vp-ref_clock=get_audio_clock(is);diff=vp->pts-ref_clock;sync_threshold=(delay>AV_SYNC_THRESHOLD)?delay:if(fabs(diff)<AV_NOSYNC_THRESHOLD){if(diff<=-sync_threshold){delay=}elseif(diff>=sync_threshold){delay=2*delay;}}is->frame_timer+=actual_delay=is->frame_timer-(av_gettime()/1000000.0);if(actual_delay<0.010){actual_delay=}schedule_refresh(is,(int)(actual_delay*1000+0.5));if(++is->pictq_rindex==_PICTURE_QUEUE_SIZE){is->pictq_rindex=0;}}}else{schedule_refresh(is,100);}},。在這里做了很多檢查首先保證現(xiàn)在的時間戳和上一個時間戳之間處以delay是有意義的如果不是的話就猜測著用上次的延遲接著ffplay0.01作為它的值也保證閾值不會比時間戳之間的間隔短最后,10,。 ponent_open中初始化幀時間frame_timer和前面的幀延遲framedelay:is->frame_timer=(double)av_gettime()/1000000.0;is->frame_last_delay=40e-3;同步:聲音時現(xiàn)在讓看一下怎樣來得到聲音時鐘??梢栽诼曇艉瘮?shù)audio_decode_frame中更新時鐘時間?,F(xiàn)在,請記住并不是每次調(diào)用這個:得到新的包的時候簡單的設(shè)置聲音時鐘為這個包的時間戳。然后,如果一個包里有許多幀,通過樣本數(shù)和采樣率來計算,所以當?shù)玫桨模篿f(pkt->pts!=AV_NOPTS_VALUE)is->audio_clock=av_q2d(is->audio_st->time_base)*pkt-}然后當處理這個包的時候pts=is-*pts_ptr=n=2*is->audio_st->codec->channels;is->audio_clock+=(double)data_size(double)(n*is->audio_st->codec-一點細節(jié):臨時函數(shù)被改成包含pts_ptr,所以要保證你已經(jīng)改了那些。這時的pts_ptr是一個用來通知audio_callback函數(shù)當前聲音包的時間戳的指針。這將在下次用來同步聲音和。現(xiàn)在可以最后來實現(xiàn)的get_audio_clock函數(shù)。它并不像得is->audio_clock值那樣簡單。注意會在每次處理它的時候設(shè)置聲音時間戳,但是如果你看了audio_callback函數(shù),它花費了時間來把數(shù)據(jù)從聲音包中移到的輸出緩沖區(qū)中。這意味著聲音時鐘中記錄的時間比實際的要早太多。所以須要檢查一下還有多少沒有寫入。下面是完整的代碼:doubleget_audio_clock(State*is){doublepts;inthw_buf_size,bytes_per_sec,pts=is-hw_buf_size=is->audio_buf_size-is->audio_buf_index;bytes_per_sec=0;n=is->audio_st->codec->channels*2;if(is->audio_st){bytes_per_sec=is->audio_st->codec->sample_rate*}if(bytes_per_sec)pts-=(double)hw_buf_size/}return}這就是了!讓編譯它:gcc-otutorial05tutorial05.c-lavutil-lavformat-lavcodec--dl-config--cflags--步,然后接下來的指導(dǎo)會查詢6:同步音同步音現(xiàn)在已經(jīng)有了一個比較像樣的器所以讓看一下還有哪些零碎的東西沒處理。上次,掩飾了一點同步問題,也就是同步音頻到而不是其它的同步方式。采用和一樣的方式:做一個時鐘來記錄視頻線程了多久,然后同步音頻到上面去。后面把音頻和都同步到外部時鐘?,F(xiàn)在要生成一個類似于上次時鐘的時鐘一個給出當前時間的值。開始,你可能會想這和使用上一幀的時間戳來更新定時一樣簡單。但是,了幀之間的時間間隔是很長的,以毫秒為計量的。解決辦法是另外一個值:在設(shè)置上一幀時間戳的時候的時間值。于是當前時間值就是PTS_of_last_frame+(current_time-time_elapsed_since_PTS_value_was_set)。這種解決方式與在函數(shù)get_audio_clock,所在在的大結(jié)構(gòu)體中放上一個雙精度浮點變量_current_pts和一個64位寬整型變量_current_pts_time。時鐘更新將被放在_refresh_timer,void_refresh_timer(void*userdata)if(is->_st)if(is->pictq_size==0){schedule_refresh(is,1);}elsevp=&is->pictq[is-is->_current_pts=vp-is->_current_pts_time=記 ponent_open函數(shù)中初始化它is->_current_pts_time=現(xiàn)在需要一種得到信息的方式doubleget__clock(State*is){doubledelta;delta=(av_gettime()-is->_current_pts_time)/1000000.0;returnis->_current_pts+delta;}提取時。但是為什么要強制使用時鐘呢?更改同步代碼以致于音頻和不會試著去相互同步。想像一下讓它像ffplay一樣有一個命令行參數(shù)。所以讓抽象一樣這件事情:做一個新的封裝函數(shù)get_master_clock,用來檢測av_sync_type變量然后決定調(diào)用get_audio_clock還是get__clock或者其它的想使用的獲得時鐘的函數(shù)甚至可以使用電腦時鐘,這個函數(shù)叫做get_external_clock:。enum#defineDEFAULT_AV_SYNC_TYPEAV_SYNC_doubleget_master_clock(State*is)if(is->av_sync_type==AV_SYNC__MASTER){returnget__clock(is);}elseif(is->av_sync_type==AV_SYNC_AUDIO_MASTER){returnget_audio_clock(is);}elsereturn}}main()is->av_sync_type=}同步音。:現(xiàn)在是最難的部分:同步音頻到時鐘的策略是測量聲音的位置,把它與時間比較然后算出需要修正多少的樣本數(shù),也就是說是否需要通過丟棄樣本的方式來加速還是需要通過插值樣本的方式來放慢?synchronize_audio的收縮或者擴展聲音樣本。然而,不想在每次發(fā)現(xiàn)有偏差的時候都進行同步,因為這樣會使同步音頻多于包。所以為函數(shù)synchronize_audio設(shè)置一個最小連續(xù)值來限定需要同步的時刻,這樣就不會總是在調(diào)整了。。:所以使用一個分數(shù)系數(shù),叫c,所以現(xiàn)在可以說得到了N個失去同步 要計算一下失去同,的長度的均值。例如,第一次調(diào)用的時候,顯示出來失去同步的長度為40ms,下次變?yōu)?0ms等等。但是不會使用一個簡單的均值,因為距離現(xiàn)在最近的值比靠前的值要重要的多。所以使用一個分數(shù)系統(tǒng),叫c,然后用這樣的公式來計算差異:diff_sum=new_diff+diff_sum*c。當準備好去找平均差異的時候用簡單的計算方式:avg_diff=diff_sum*(1-c)。,想要的信息,這里是一個解釋 /ffmpeg/weightedmean.html /ffmpeg/weightedmean.txt下面是的函數(shù):intsynchronize_audio(State*is,shortintsamples_size,doublepts)intdoublen=2*is->audio_st->codec-if(is->av_sync_type!=AV_SYNC_AUDIO_MASTER){doublediff,avg_diff;intwanted_size,min_size,max_size,ref_clock=diff=get_audio_clock(is)-if(diff<AV_NOSYNC_THRESHOLD)//accumulatetheis->audio_diff_cum=diff+is-*is-if(is->audio_diff_avg_count<AUDIO_DIFF_AVG_NB){}elseavg_diff=is->audio_diff_cum*(1.0-is-}}elseis->audio_diff_avg_count=is->audio_diff_cum=}}return}現(xiàn)在已經(jīng)做得很好已經(jīng)近似的知道如何用或者其它的時鐘來調(diào)整音頻了。所以讓來計算一下要在添加和砍掉多少樣本,并且如何在“Shrinking/expandingbuffercode”部分來寫上代碼:if(fabs(avg_diff)>=is->audio_diff_threshold){wanted_size=samples_size+((
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度國際文化交流項目志愿者聘用合同
- 2025版民宿民宿餐飲服務(wù)合同示范4篇
- 2025年度房地產(chǎn)公司股權(quán)轉(zhuǎn)讓與市場推廣合同
- 2025年度個人車位租賃服務(wù)合同范本2篇
- 2025年度沐足行業(yè)員工勞動合同模板(含保密協(xié)議)4篇
- 林綿綿《韓娛離婚協(xié)議》2025年度網(wǎng)絡(luò)劇改編權(quán)轉(zhuǎn)讓合同8篇
- 二零二五年度個人現(xiàn)金借款合同標準版2篇
- 二零二五年度農(nóng)產(chǎn)品品牌授權(quán)使用合同8篇
- 二零二五年度農(nóng)家樂鄉(xiāng)村旅游扶貧項目合作合同4篇
- 二零二五年度文化旅游產(chǎn)業(yè)投資借款合同大全4篇
- 2022年中國電信維護崗位認證動力專業(yè)考試題庫大全-上(單選、多選題)
- 紀委辦案安全培訓(xùn)課件
- 超市連鎖行業(yè)招商策劃
- 醫(yī)藥高等數(shù)學(xué)智慧樹知到課后章節(jié)答案2023年下浙江中醫(yī)藥大學(xué)
- 城市道路智慧路燈項目 投標方案(技術(shù)標)
- 初中英語-Unit2 My dream job(writing)教學(xué)設(shè)計學(xué)情分析教材分析課后反思
- 【公司利潤質(zhì)量研究國內(nèi)外文獻綜述3400字】
- 工行全國地區(qū)碼
- 新疆2022年中考物理試卷及答案
- 地暖工程監(jiān)理實施細則
- 頂部板式吊耳計算HGT-20574-2018
評論
0/150
提交評論