ffmpegtutorial中文版如何用編寫一個簡單播放器詳細步驟介紹轉(zhuǎn)載_第1頁
ffmpegtutorial中文版如何用編寫一個簡單播放器詳細步驟介紹轉(zhuǎn)載_第2頁
ffmpegtutorial中文版如何用編寫一個簡單播放器詳細步驟介紹轉(zhuǎn)載_第3頁
ffmpegtutorial中文版如何用編寫一個簡單播放器詳細步驟介紹轉(zhuǎn)載_第4頁
ffmpegtutorial中文版如何用編寫一個簡單播放器詳細步驟介紹轉(zhuǎn)載_第5頁
已閱讀5頁,還剩44頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

FFmpeg,FFMPEG是一個很好的庫,可以用來創(chuàng)建視頻應用或者生成特定的工具。FFMPEG幾乎為你把所有的繁重工作都做了,比如解碼、編碼、復用和解復用。這使得解碼幾乎所有你能用到的格式,當然也包括編碼多種格式。FFMPEGffplayCffmpegMartinBohme我將從那里開發(fā)一個可以使用的視頻播放器。在每一個指導中,我將介紹一個或者兩個新的思想并且講解我們?nèi)绾蝸韺崿F(xiàn)它。每一個指導都會有一個C源文SDL太多,因為我將在這篇指導中介紹很多這樣的概念。Container,容器的類型決定了信息被存放在文件中的位置。AVIQuicktime個流只是一種想像出來的詞語,用來表示一連串的通過時間來串連的數(shù)據(jù)元素)FrameCODEC。DivxMP320取里面的視頻流,而且我們對幀的操作將是把這個幀寫到一個PPM文件中。#include<avcodec.h>intmain(intargc,charg{這里注冊了所有的文件格式和編解碼器的庫,所以它們將被自動的使用在被打AVFormatContext//Openvideoif(av_open_input_file(&pFormatCtx,argv[1],NULL,0,NULL)!=0)return-1;//Couldn'topenfile//Retrievestreaminformationreturn-1;//Couldn'tfindstream//Dumpinformationaboutfileontostandarderrordump_format(pFormatCtx,0,argv[1],0);所以讓我們先跳過它直到我們找到一個視頻流。intAVCodecContext//Findthefirstvideostreamfor(i=0;i<pFormatCtx->nb_streams;}//Getapointertothecodeccontextforthevideostream個指向他的指針。但是我們必需要找到真正的編解碼器并且打開它:AVCodec//Findthedecoderforthevideostreamif(pCodec==NULL){fprintf(stderr,"Unsupportedcodec!\n");return-1;//Codecnotfound}//Opencodecreturn-1;//CouldnotopenCODEC_FLAG_TRUNCATEDpCodecCtx->flagshack:pCodecCtx->time_base在已經(jīng)保存了幀率的信息。time_base幀率(例如NTSC使用29.97fps)。AVFrame*pFrame;//Allocatevideoframe//AllocateanAVFramestructurereturn-intnumBytes;//Determinerequiredbuffersizeandallocatebuffer存泄漏,重復釋放或者其它malloc的問題所困擾。現(xiàn)在我們使用avpicture_fill來把幀和我們新申請的內(nèi)存來結(jié)合。關于構(gòu)體的開始部分與AVPicture結(jié)構(gòu)體是一樣的。//Assignappropriatepartsofbuffertoimageplanesin//NotethatpFrameRGBisanAVFrame,butAVFrameisa//ofavpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);intAVPacketwhile(av_read_frame(pFormatCtx,&packet)>=0)//Isthisapacketfromthevideostream?if(packet.stream_index==videoStream){//Decodevideoavcodec_decode_video(pCodecCtx,pFrame,packet.data,//Didwegetavideoframe?if(frameFinished){//Converttheimagefromitsnativeformattoimg_convert((AVPicture*)pFrameRGB,PIX_FMT_RGB24,(AVPicture*)pFrame,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height);//SavetheframetodiskpCodecCtx->height,i);}}//Freethepacketthatwasallocatedbyav_read_frame}當我們得到下一幀的時候,avcodec_decode_video()為我們設置了幀結(jié)束標志frameFinished。最后,我們使用img_convert()函數(shù)來把幀從原始格式傳遞給我們的SaveFrame函數(shù)。中。我們將生成一個簡單的PPM格式文件,請相信,它是可以工作的。voidSaveFrame(AVFrame*pFrame,intwidth,intheight,int{FILEinty;//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}#ff0000#ff0000....就表示了了個紅色的屏幕。(高度以及最大的RGB值的大小。//FreetheRGBimage//FreetheYUVframe//Closethecodec//Closethevideofilereturn你會注意到我們使用av_freeavcode_alloc_framav_mallocgccotutorial01tutorial01.clavutillavformatlavcodeclzlmFFmpeg,2個Y1個U1個V)。SDL的YUVYUV4YUVYV12YUV420PYUVYV12UV4204:2:0的色度信息只有原來的1/4。這是一種節(jié)省帶寬的好方式,因為人眼感覺不到PY,UV分量分別在不同的數(shù)組中。FFMPEGYUV420P,但是現(xiàn)在很YUV420PYUV420P1SaveFrame()函數(shù)替換掉,讓它直接輸出我們#include<SDL.h>#include<SDL_thread.h>if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER)){fprintf(stderr,"CouldnotinitializeSDL-%s\n",SDL_GetError());exit(1);}叫做面surface。SDL_Surface*screen;screen=SDL_SetVideoMode(pCodecCtx->width,pCodecCtx->height,0,0);if(!screen){fprintf(stderr,"SDL:couldnotsetvideomode-exiting\n");exit(1);}示使用和當前一樣的深度。(OSX現(xiàn)在我們在屏幕上來創(chuàng)建一個YUV覆蓋以便于我們輸入視頻上去: *bmp;bmp=SDL_CreateYUVOverlay(pCodecCtx->width, 設置其數(shù)據(jù)指針和行尺寸來為我們的YUV覆蓋服務:if(frameFinished){ pict.data[0]=bmp->pixels[0]; pict.data[1]= pict.data[2] pict.linesize[0]= pict.linesize[2]=bmp->pitches[1]; theimageintoYUVformatthatSDLuses (AVPicture*)pFrame, SDL_UnlockYUVOverlay(bmp);}一樣我們使用img_convert來把格式轉(zhuǎn)換成PIX_FMT_YUV420P。SDL_Rectrect;if(frameFinished){//ConverttheimageintoformatthatSDLuses rect.y=0; rect.w=pCodecCtx->width; rect.h= SDL_DisplayYUVOverlay(bmp,&rect);}SDLSDLSDLSDL_QUIT

switch(event.type){

SDL庫進行編譯的最好方式為:gcc-otutorial02tutorial02.c-lavutil-lavformat-lavcodec-lz-\`sdl-config--cflags--SDL(5),我們將花足夠情要處理:音頻!特定的采樣率來進行錄制,采樣率表示以多快的速度來播放這段樣本流,它的的采樣率。此外,大多音頻有不只一個通道來表示立體聲或者環(huán)繞。例如,如的樣本――這意味著它將不會把立體聲分割開來。斷地調(diào)用這個回調(diào)函數(shù)并且要求它來向聲音緩沖填入一個特定的數(shù)量的字節(jié)。當我們把這些信息放到SDL_AudioSpec結(jié)構(gòu)體中后,我們調(diào)用函數(shù)SDL_OpenAudio()就會打開聲音設備并且給我們送回另外一個AudioSpec結(jié)構(gòu)//Findthefirstvideostreamfor(i=0;i<pFormatCtx->nb_streams;i++){}audioStream<0){}}return-1;//Didn'tfindavideostreamAVCodecContextwanted_spec.freq=aCodecCtx->sample_rate;wanted_spec.format=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)<{fprintf(stderr,"SDL_OpenAudio:%s\n",SDL_GetError());return-1;}forma這些格式是由avcodec_decode_audio2為我們給出來的輸入音頻的格式。區(qū)的尺寸。一個比較合適的值在5128192之間;ffplay1024。 if(!aCodec){fprintf(stderr,"Unsupportedcodec!\n");return-1;}avcodec_open(aCodecCtx,據(jù)。所以我們要做的是創(chuàng)建一個包的隊列queue。在ffmpeg中有一個叫typedefstruct{AVPacketList*first_pkt,*last_pkt;intnb_packets;intSDL_mutexSDL_cond}首先,我們應當指出nb_packetssize不一樣的--size表示我們從列,但是我們將把這部分也來討論從而可以學習到SDL的函數(shù)。voidpacket_queue_init(PacketQueue{memset(q,0,sizeof(PacketQueue));q->mutex=SDL_CreateMutex();q->cond=}intpacket_queue_put(PacketQueue*q,AVPacket{AVPacketListif(av_dup_packet(pkt)<{return-}pkt1=av_malloc(sizeof(AVPacketList));if(!pkt1)return-pkt1->pkt=*pkt;pkt1->next=NULL;if(!q->last_pkt)q->first_pkt=pkt1;q->last_pkt->next=pkt1;q->last_pkt=pkt1;q->size+=pkt1->pkt.size;return0;}數(shù)阻塞block的(例如一直等到隊列中有數(shù)據(jù))。intquit=staticintpacket_queue_get(PacketQueue*q,AVPacket*pkt,int{AVPacketList*pkt1;intret;for(;;){{ret=-}pkt1=q->first_pkt;if(pkt1){q->first_pkt=pkt1->next;if(!q->first_pkt)q->last_pkt=NULL;q->size-=pkt1-*pkt=pkt1->pkt;ret=1;}elseif{ret=0;}elseSDL_CondWait(q->cond,q-}}returnret;}基本上,所有的CondWait只等待從SDL_CondSignal()函數(shù)(或者,SDL_CondWait()函數(shù)也為我們做了解鎖quit,我們用它來保證還沒有設置程序退int{return}main(){caseSDL_QUIT:quit=1;必需要設置quit標志為1。PacketQueueaudioq;main()avcodec_open(aCodecCtx,while(av_read_frame(pFormatCtx,&packet)>=0)//Isthisapacketfromthevideostream?if(packet.stream_index==videoStream){//Decodevideo}}else{packet_queue_put(&audioq,}{}audio_callbackvoidcallback(void*userdata,Uint8*stream,intlen),緩沖區(qū)指針,len是緩沖區(qū)的大小。下面就是代碼:voidaudio_callback(void*userdata,Uint8*stream,int{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;}}len小以便于有一個比較好的緩沖,這個聲音幀的大小是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=len1=avcodec_decode_audio2(aCodecCtx,(int16_t*)audio_buf,audio_pkt_data,audio_pkt_size);if(len1<0){audio_pkt_size=0;}audio_pkt_data+=len1;audio_pkt_size-=len1;if(data_size<=0){}return}{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ù)。我們從隊列中取出包,并且保存它的信息。然后,一旦我們有了可以使用的包,我們就調(diào)用函數(shù)avcodec_decode_audio2(),它的功能就像它的姐妹函數(shù)所以你可能要調(diào)用很多次來解碼出包中所有的數(shù)據(jù)。同時也要記住進行指針audio_bufSDL8ffmpeg示解碼使用的數(shù)據(jù)的在包中的大小,data_size表示實際返回的原始聲音數(shù)據(jù)的它保存到下一次。如果我們完成了一個包的處理,我們最后要釋放它。gcc-otutorial03tutorial03.c-lavutil-lavformat-lavcodec-lz-lm`sdl-config--cflags--中,但是聲音設備卻會按照原來指定的采樣率來進行播放。得程序更加更加易于控制和模塊化。在我們開始同步音視頻之前,我們需要讓FFmpeg,}else}}//Isthisapacketfromthevideostream?if(packet->stream_index==is->videoStream){}elseif(packet->stream_index==is-{packet_queue_put(&is->audioq,}{}}pbByteIOContext類檢測結(jié)構(gòu)體并發(fā)現(xiàn)是否有些讀取文件錯誤。代碼是有益的,因為它指示出了如何驅(qū)動事件--后面我們將顯示影像。while(!is-{}SDL_Eventevent;event.type=FF_QUIT_EVENT;event.user.data1=is;}return的退出標志quit。queue_pictureintvideo_thread(void{VideoState*is=(VideoState*)arg;AVPacketpkt1,*packet=&pkt1;intlen1,frameFinished;AVFrame*pFrame;pFrame=avcodec_alloc_frame();for(;;){if(packet_queue_get(&is->videoq,packet,1)<0)//meanswequitgettingpackets}//Decodevideolen1=avcodec_decode_video(is->video_st->codec,pFrame,packet->data,packet-//Didwegetavideoframe?if(frameFinished){if(queue_picture(is,pFrame)<{}}}return0;}avcodec_decode_videotypedefstruct{SDL_Overlay*bmp;intwidth,height;intallocated;}在還不太清楚原因;我相信是為了避免在其它線程中調(diào)用SDL覆蓋函數(shù)的原intqueue_picture(VideoState*is,AVFrame{VideoPicture*vp;intdst_pix_fmt;AVPicturepict;while(is->pictq_size>=VIDEO_PICTURE_QUEUE_SIZE!is->quit)}return-//windexissetto0initiallyvp=&is->pictq[is->pictq_windex];if(!vp->bmpvp->width!=is->video_st->codec->width||vp->height!=is->video_st->codec->height){SDL_Eventevent;vp->allocated=0;event.type=FF_ALLOC_EVENT;event.user.data1=is;while(!vp->allocated&&!is->quit){}if(is->quit){return-}}{);{caseFF_ALLOC_EVENT:voidalloc_picture(void*userdata)VideoState*is=(VideoState*)userdata;VideoPicture*vp;vp=&is->pictq[is->pictq_windex];if(vp->bmp){//wealreadyhaveonemakeanother,bigger/smaller}//AllocateaplacetoputourYUVimageonthatvp->bmp=SDL_CreateYUVOverlay(is->video_st->codec->width,vp->width=is->video_st->codec->width;vp->height=is->video_st->codec->height;vp->allocated=1;}queue_pictureintqueue_picture(VideoState*is,AVFrame*pFrame)if(vp-dst_pix_fmt=pict.data[0]=vp->bmp->pixels[0];pict.data[1]=vp->bmp->pixels[2];pict.data[2]=vp->bmp->pixels[1];pict.linesize[0]=vp->bmp->pitches[0];pict.linesize[1]=vp->bmp->pitches[2];pict.linesize[2]=vp->bmp->pitches[1];//ConverttheimageintoYUVformatthatSDLimg_convert(&pict,(AVPicture*)pFrame,is->video_st->codec-if(++is->pictq_windex=={is->pictq_windex=}}return}YUV一點只是簡單的給隊列加1。這個隊列在寫的時候會一直寫入到滿為止,在讀的is->pictq_size需要鎖定它。這里我們做的是增加寫指針(在必要的時候采用輪轉(zhuǎn)的方式),用schedule_refresh()函數(shù)嗎?讓我們看一下實際中是如何做的:staticvoidschedule_refresh(VideoState*is,int{SDL_AddTimer(delay,sdl_refresh_timer_cb,}把一幀從圖像隊列中顯示到屏幕上。staticUint32sdl_refresh_timer_cb(Uint32interval,void{SDL_Eventevent.type=FF_REFRESH_EVENT;event.user.data1=opaque;return}這里向隊列中寫入了一個現(xiàn)在很熟悉的事件。FF_REFRESH_EVENT被定義成for(;;){switch(event.type){casevoidvideo_refresh_timer(void*userdata){VideoState*is=(VideoState*)userdata;VideoPicture*vp;if(is->video_st)if(is->pictq_size=={schedule_refresh(is,}elsevp=&is->pictq[is-schedule_refresh(is,if(++is->pictq_rindex=={is->pictq_rindex=}}}else{schedule_refresh(is,}}video_display把相應的值寫入到80。從技術上來講,你可以猜測并驗證這個值,并且為每個電影重新編譯程序,但是:1)過一段時間它會漂移;2)這種方式是很笨的。我們將在后面來討論voidvideo_display(VideoState{SDL_Rectrect;VideoPicture*vp;AVPicturepict;intw,h,x,y;inti;vp=&is->pictq[is->pictq_rindex];if(vp->bmp){if(is->video_st->codec->sample_aspect_ratio.num=={aspect_ratio=}elseaspect_ratio=av_q2d(is->video_st->codec-*is->video_st->codec->width/is->video_st->codec-}if(aspect_ratio<=0.0)aspect_ratio=(float)is->video_st->codec->width/}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=}}aspectratio,表示方式為寬度除以高度。某些編解碼器1x1到中心位置,接著調(diào)用SDL_DisplayYUVOverlay()函數(shù)。VideoStruct回調(diào)函數(shù)。VideoState*global_video_state;intdecode_interrupt_cb(void){return(global_video_state&&global_video_state-}gcc-otutorial04tutorial04.c-lavutil-lavformat-lavcodec-lz-lm`sdl-config--cflags--PTS幀和乘以幀率的方式來同步視頻,那么就很有可能會失去同步。于是作為一種(Bbidrectional)IP(I關鍵幀,P)。IPavcodec_decode_video以后會得不到一幀圖像。所以對于一個電影,幀是這樣來顯示的:IBBPB什么我們會有一個解碼時間戳和一個顯示時間戳的原因。解碼時間戳告訴我們PTS:142DTS:123Stream:IPBPTSPTS,這樣我們才能知道什么時候來顯示它。然而,我們從avcodec_decode_video()函數(shù)中得到的幀只是一個AVFrame,其中并沒有包含有用的PTS值(注意:AVFrameffmpegavcodec_decode_video()函數(shù)處理的包的DTS個信息。avcodec_decode_video()來計算出哪個包是一幀的第一個包。怎樣,avcodec_decode_video()將調(diào)用一個函數(shù)來為一幀申請一個緩沖。當然,ffmpeg存的函數(shù)。所以我們制作了一個新的函數(shù)來保存一個包的時間戳。PTS視頻文件也不是完好的。所以,我們有三種選擇:同步音頻到視頻,同步視頻步視頻到音頻。們得到了解碼線程輸出到隊列中的包。這里我們需要的是從avcodec_decode_video次處理的包中得到DTS,這是很容易的:for(;;){if(packet_queue_get(&is->videoq,packet,1)<0)//meanswequitgettingpackets}pts=//Decodevideolen1=avcodec_decode_video(is->video_st-packet->data,packet->size);if(packet->dts!={pts=packet-}{pts=}pts*=av_q2d(is->video_st-intget_buffer(structAVCodecContext*c,AVFrame*pic);voidrelease_buffer(structAVCodecContext*c,AVFrame到AVFrame結(jié)構(gòu)體難理解的變量中去。所以一開始,這就是我們的函數(shù):uint64_tglobal_video_pkt_pts=intour_get_buffer(structAVCodecContext*c,AVFrame{intret=avcodec_default_get_buffer(c,pic);uint64_t*pts=av_malloc(sizeof(uint64_t));*pts=global_video_pkt_pts;pic->opaque=pts;return}voidour_release_buffer(structAVCodecContext*c,AVFrame{if(pic)av_freep(&pic->opaque);avcodec_default_release_buffer(c,pic);}avcodec_default_get_bufferavcodec_default_release_buffer但把內(nèi)存釋放而且把指針設置為NULL。訴ffmpeg如何去做:codecCtx->get_buffer=our_get_buffer;codecCtx->release_buffer=our_release_buffer;for(;;)if(packet_queue_get(&is->videoq,packet,1)<0)//meanswequitgettingpackets}pts=//SaveglobalptstobestoredinpFrameinfirstcallglobal_video_pkt_pts=packet->pts;//Decodevideolen1=avcodec_decode_video(is->video_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!={pts=packet-}{pts=}pts*=av_q2d(is->video_st-型來保存的。這個值是一個時間戳相當于時間的度量,用來以流的time_base4224(這里并不完全正確)。我們可以通過除以幀率來把這個值轉(zhuǎn)化為秒。流中的time_base值表示乘以time_base。PTSvideo_clocktypedefstructVideoState video_clock;{if(pts!=0){is->video_clock=}elsepts=is-}frame_delay=av_q2d(is->video_st->codec-frame_delay+=src_frame->repeat_pict*(frame_delay*0.5);is->video_clock+=frame_delay;return}時間戳參數(shù)pts://Didwegetavideoframe?if(frameFinished){pts=synchronize_video(is,pFrame,pts);if(queue_picture(is,pFrame,pts)<0){}}queue_picturepts保存到typedefstructVideoPicturedouble}intqueue_picture(VideoState*is,AVFrame*pFrame,doublepts)...stuff...if(vp->bmp)...convertpicture...vp->pts=pts;...alertqueue}將會算出實際的值。戳的時間。同時,我們需要同步視頻到音頻。我們將設置一個音頻時間audio的函數(shù)get_audio_clock。一旦我們有了這個值,我們在音頻和視頻失去同步的時候應該做些什么呢?簡單而有點笨的辦法是試著用跳過正確幀或者其它的影播放中所有的延時。換句話說,這個frame_timer就是指我們什么時候來顯后使用那個值來調(diào)度下一次刷新。這可能有點難以理解,所以請認真研究代碼:voidvideo_refresh_timer(void*userdata){VideoState*is=(VideoStateVideoPicturedoubleactual_delay,delay,sync_threshold,ref_clock,if(is->video_st)if(is->pictq_size=={schedule_refresh(is,}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)<{if(diff<=-sync_threshold){delay=0;}elseif(diff>={delay=2*}}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=={is->pictq_rindex=}}}else{schedule_refresh(is,}}們把最小的刷新值設置為10毫秒。記在函數(shù)streame_component_openframe_timer遲framedelay:is->frame_timer=(double)av_gettime()/1000000.0;is->frame_last_delay=40e-3;audio_decode_frameif(pkt->pts!=AV_NOPTS_VALUE)}pts=is-*pts_ptr=n=2*is->audio_st->codec->channels;is->audio_clock+=(double)data_size(double)(n*is->audio_st->codec-is->audio_clockdoubleget_audio_clock(VideoState*is)doubleinthw_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--lm`sdl-config--cflags--指導6西沒處理。上次,我們掩飾了一點同步問題,也就是同步音頻到視頻而不是其它的同步方式。我們將采用和視頻一樣的方式:做一個內(nèi)部視頻時鐘來記錄視播放時間的內(nèi)部值。開始,你可能會想這和使用上一幀的時間戳來更新定時器當前視頻時間值就是PTS_of_last_frame+(current_time-get_audio_clock中的方式很類似。和一個64位寬整型變量video_current_pts_time。時鐘更新將被放在video_refresh_timer函數(shù)中。voidvideo_refresh_timer(void*userdata)if(is->video_st)if(is->pictq_size=={schedule_refresh(is,}elsevp=&is->pictq[is-is->video_current_pts=vp-is->video_current_pts_timeav_gettime();is->video_current_pts_timeav_gettime();doubleget_video_clock(VideoState{doubledelta=(av_gettime()-is->video_current_pts_time)/1000000.0;returnis->video_current_pts+delta;}用來檢測av_sync_type變量然后決定調(diào)用get_audio_clock還是時鐘,這個函數(shù)我們叫做get_external_clock:enum#defineDEFAULT_AV_SYNC_TYPEdoubleget_master_clock(VideoState*is){if(is->av_sync_type==AV_SYNC_VIDEO_MASTER){return}elseif(is->av_sync_type=={return}elsereturn}}main()is->av_sync_type=}通過丟棄樣本的方式來加速播放還是需要通過插值樣本的方式來放慢播放?synchronize_audio步,因為這樣會使同步音頻多于視頻包。所以我們?yōu)楹瘮?shù)synchronize_audio的長度的均值。例如,第一次調(diào)用的時候,顯示出來我們失去同步的長度為40ms,50ms最近的值比靠前的值要重要的多。所以我們將使用一個分數(shù)系統(tǒng),叫c,然后用這樣的公式來計算差異:diff_sum=new_diff+diff_sum*c。當我們準備想要更多的信息,這里是一個解釋/ffmpeg/weightedmean.html或者在/ffmpeg/weightedmean.txt里。intsynchronize_audio(VideoState*is,short*samples,intsamples_size,doublepts){intdoublen=2*is->audio_st->codec-if(is->av_sync_type!={doublediff,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<{is-}elseavg_diff=is->audio_diff_cum*(1.0-is-}}elseis->audio_diff_avg_count=is->audio_diff_cum=}}return}“Shrinking/expandingbuffercode”部分來寫上代碼:if(fabs(avg_diff)>=is-{wanted_size=samples_size((int)(diff*is->audio_st->codec->sample_rate)*min_size=samples_size*((100-/max_size=samples_size*((100+/if(wanted_size<{wanted_size=}elseif(wanted_size>{wanted_size=}audio_length*(sample_rate*ofchannels*2)audio_length者減少后的聲音樣本數(shù)。我們也可以設置一個范圍來限定我們一次進行修正的但是如果我們想讓它變大,我們不能只是讓樣本大小變大,因為在緩沖區(qū)中沒if(wanted_size<{samples_size=}elseif(wanted_size>{uint8_t*samples_end,*q;intnb;nb=(samples_size-samples_end=(uint8_t*)samples+samples_size-n;q=samples_end+n;while(nb>0)memcpy(q,samples_end,n);q+=n;nb-=}samples_size=}voidaudio_callback(void*userdata,Uint8*stream,intlen)VideoState*is=(VideoState*)userdata;intlen1,audio_size;doublewhile(len>0)if(is->audio_buf_index>=is->audio_buf_size)audio_size=audio_decode_frame(is,is->audio_buf,sizeof(is->audio_buf),&pts);if(audio_size<0)is->audio_buf_size=memset(is->audio_buf,0,is-}elseaudio_size=synchronize_audio(is,(int16_t*)is->audio_buf,audio_size,pts);is->audio_buf_size=if(is->av_sync_type!={ref_clock=get_master_clock(is);diff=vp->pts-ref_clock;sync_threshold=(delay>AV_SYNC_THRESHOLD)?delay:if(fabs(diff)<{if(diff<=-sync_threshold){delay=0;}elseif(diff>={delay=2*}}}gcc-otutorial06tutorial06.c-lavutil-lavformat-lavcodec--lm`sdl-config--cflags--以我們需要設置我們的主循環(huán)來捕捉鍵盤事件。然而當我們捕捉到鍵盤事件后 for(;;)doubleincr,{case{caseSDLK_LEFT:incr=-10.0;gotodo_seek;incr=10.0;gotoincr=60.0;gotodo_seek;incr=-60.0;gotodo_seek;pos=get_master_clock(global_video_state);pos+=incr;stream_seek(global_video_state,(int64_t)(pos*AV_TIME_BASE),}}為了檢測按鍵,我們先查了一下是否有SDL_KEYDOWN事件。然后我們使用get_master_clockseconds=frames*time_base(fps)。后面我們來看一下為什么要把這個值進行一下轉(zhuǎn)換。voidstream_seek(VideoState*is,int64_tpos,intrel){if(!is->seek_req){is->seek_pos=pos;is->seek_flags=rel<0?AVSEEK_FLAG_BACKWARD:0;is->seek_req=1;}}文件中標記了一個叫做“seekstuff

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論