




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
FFmpeg,FFMPEG是一個(gè)很好的庫(kù),可以用來(lái)創(chuàng)建視頻應(yīng)用或者生成特定的工具。FFMPEG幾乎為你把所有的繁重工作都做了,比如解碼、編碼、復(fù)用和解復(fù)用。這使得解碼幾乎所有你能用到的格式,當(dāng)然也包括編碼多種格式。FFMPEGffplayCffmpegMartinBohme我將從那里開(kāi)發(fā)一個(gè)可以使用的視頻播放器。在每一個(gè)指導(dǎo)中,我將介紹一個(gè)或者兩個(gè)新的思想并且講解我們?nèi)绾蝸?lái)實(shí)現(xiàn)它。每一個(gè)指導(dǎo)都會(huì)有一個(gè)C源文SDL太多,因?yàn)槲覍⒃谶@篇指導(dǎo)中介紹很多這樣的概念。Container,容器的類型決定了信息被存放在文件中的位置。AVIQuicktime個(gè)流只是一種想像出來(lái)的詞語(yǔ),用來(lái)表示一連串的通過(guò)時(shí)間來(lái)串連的數(shù)據(jù)元素)FrameCODEC。DivxMP320取里面的視頻流,而且我們對(duì)幀的操作將是把這個(gè)幀寫到一個(gè)PPM文件中。#include<avcodec.h>intmain(intargc,charg{這里注冊(cè)了所有的文件格式和編解碼器的庫(kù),所以它們將被自動(dòng)的使用在被打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);所以讓我們先跳過(guò)它直到我們找到一個(gè)視頻流。intAVCodecContext//Findthefirstvideostreamfor(i=0;i<pFormatCtx->nb_streams;}//Getapointertothecodeccontextforthevideostream個(gè)指向他的指針。但是我們必需要找到真正的編解碼器并且打開(kāi)它: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存泄漏,重復(fù)釋放或者其它malloc的問(wèn)題所困擾?,F(xiàn)在我們使用avpicture_fill來(lái)把幀和我們新申請(qǐng)的內(nèi)存來(lái)結(jié)合。關(guān)于構(gòu)體的開(kāi)始部分與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}當(dāng)我們得到下一幀的時(shí)候,avcodec_decode_video()為我們?cè)O(shè)置了幀結(jié)束標(biāo)志frameFinished。最后,我們使用img_convert()函數(shù)來(lái)把幀從原始格式傳遞給我們的SaveFrame函數(shù)。中。我們將生成一個(gè)簡(jiǎn)單的PPM格式文件,請(qǐng)相信,它是可以工作的。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....就表示了了個(gè)紅色的屏幕。(高度以及最大的RGB值的大小。//FreetheRGBimage//FreetheYUVframe//Closethecodec//Closethevideofilereturn你會(huì)注意到我們使用av_freeavcode_alloc_framav_mallocgccotutorial01tutorial01.clavutillavformatlavcodeclzlmFFmpeg,2個(gè)Y1個(gè)U1個(gè)V)。SDL的YUVYUV4YUVYV12YUV420PYUVYV12UV4204:2:0的色度信息只有原來(lái)的1/4。這是一種節(jié)省帶寬的好方式,因?yàn)槿搜鄹杏X(jué)不到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);}示使用和當(dāng)前一樣的深度。(OSX現(xiàn)在我們?cè)谄聊簧蟻?lái)創(chuàng)建一個(gè)YUV覆蓋以便于我們輸入視頻上去: *bmp;bmp=SDL_CreateYUVOverlay(pCodecCtx->width, 設(shè)置其數(shù)據(jù)指針和行尺寸來(lái)為我們的YUV覆蓋服務(wù):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來(lái)把格式轉(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庫(kù)進(jìn)行編譯的最好方式為:gcc-otutorial02tutorial02.c-lavutil-lavformat-lavcodec-lz-\`sdl-config--cflags--SDL(5),我們將花足夠情要處理:音頻!特定的采樣率來(lái)進(jìn)行錄制,采樣率表示以多快的速度來(lái)播放這段樣本流,它的的采樣率。此外,大多音頻有不只一個(gè)通道來(lái)表示立體聲或者環(huán)繞。例如,如的樣本――這意味著它將不會(huì)把立體聲分割開(kāi)來(lái)。斷地調(diào)用這個(gè)回調(diào)函數(shù)并且要求它來(lái)向聲音緩沖填入一個(gè)特定的數(shù)量的字節(jié)。當(dāng)我們把這些信息放到SDL_AudioSpec結(jié)構(gòu)體中后,我們調(diào)用函數(shù)SDL_OpenAudio()就會(huì)打開(kāi)聲音設(shè)備并且給我們送回另外一個(gè)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為我們給出來(lái)的輸入音頻的格式。區(qū)的尺寸。一個(gè)比較合適的值在5128192之間;ffplay1024。 if(!aCodec){fprintf(stderr,"Unsupportedcodec!\n");return-1;}avcodec_open(aCodecCtx,據(jù)。所以我們要做的是創(chuàng)建一個(gè)包的隊(duì)列queue。在ffmpeg中有一個(gè)叫typedefstruct{AVPacketList*first_pkt,*last_pkt;intnb_packets;intSDL_mutexSDL_cond}首先,我們應(yīng)當(dāng)指出nb_packetssize不一樣的--size表示我們從列,但是我們將把這部分也來(lái)討論從而可以學(xué)習(xí)到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的(例如一直等到隊(duì)列中有數(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,我們用它來(lái)保證還沒(méi)有設(shè)置程序退int{return}main(){caseSDL_QUIT:quit=1;必需要設(shè)置quit標(biāo)志為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小以便于有一個(gè)比較好的緩沖,這個(gè)聲音幀的大小是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=}}整個(gè)過(guò)程實(shí)際上從函數(shù)的尾部開(kāi)始,在這里我們調(diào)用了packet_queue_get()函數(shù)。我們從隊(duì)列中取出包,并且保存它的信息。然后,一旦我們有了可以使用的包,我們就調(diào)用函數(shù)avcodec_decode_audio2(),它的功能就像它的姐妹函數(shù)所以你可能要調(diào)用很多次來(lái)解碼出包中所有的數(shù)據(jù)。同時(shí)也要記住進(jìn)行指針audio_bufSDL8ffmpeg示解碼使用的數(shù)據(jù)的在包中的大小,data_size表示實(shí)際返回的原始聲音數(shù)據(jù)的它保存到下一次。如果我們完成了一個(gè)包的處理,我們最后要釋放它。gcc-otutorial03tutorial03.c-lavutil-lavformat-lavcodec-lz-lm`sdl-config--cflags--中,但是聲音設(shè)備卻會(huì)按照原來(lái)指定的采樣率來(lái)進(jìn)行播放。得程序更加更加易于控制和模塊化。在我們開(kāi)始同步音視頻之前,我們需要讓FFmpeg,}else}}//Isthisapacketfromthevideostream?if(packet->stream_index==is->videoStream){}elseif(packet->stream_index==is-{packet_queue_put(&is->audioq,}{}}pbByteIOContext類檢測(cè)結(jié)構(gòu)體并發(fā)現(xiàn)是否有些讀取文件錯(cuò)誤。代碼是有益的,因?yàn)樗甘境隽巳绾悟?qū)動(dòng)事件--后面我們將顯示影像。while(!is-{}SDL_Eventevent;event.type=FF_QUIT_EVENT;event.user.data1=is;}return的退出標(biāo)志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一點(diǎn)只是簡(jiǎn)單的給隊(duì)列加1。這個(gè)隊(duì)列在寫的時(shí)候會(huì)一直寫入到滿為止,在讀的is->pictq_size需要鎖定它。這里我們做的是增加寫指針(在必要的時(shí)候采用輪轉(zhuǎn)的方式),用schedule_refresh()函數(shù)嗎?讓我們看一下實(shí)際中是如何做的:staticvoidschedule_refresh(VideoState*is,int{SDL_AddTimer(delay,sdl_refresh_timer_cb,}把一幀從圖像隊(duì)列中顯示到屏幕上。staticUint32sdl_refresh_timer_cb(Uint32interval,void{SDL_Eventevent.type=FF_REFRESH_EVENT;event.user.data1=opaque;return}這里向隊(duì)列中寫入了一個(gè)現(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把相應(yīng)的值寫入到80。從技術(shù)上來(lái)講,你可以猜測(cè)并驗(yàn)證這個(gè)值,并且為每個(gè)電影重新編譯程序,但是:1)過(guò)一段時(shí)間它會(huì)漂移;2)這種方式是很笨的。我們將在后面來(lái)討論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幀和乘以幀率的方式來(lái)同步視頻,那么就很有可能會(huì)失去同步。于是作為一種(Bbidrectional)IP(I關(guān)鍵幀,P)。IPavcodec_decode_video以后會(huì)得不到一幀圖像。所以對(duì)于一個(gè)電影,幀是這樣來(lái)顯示的:IBBPB什么我們會(huì)有一個(gè)解碼時(shí)間戳和一個(gè)顯示時(shí)間戳的原因。解碼時(shí)間戳告訴我們PTS:142DTS:123Stream:IPBPTSPTS,這樣我們才能知道什么時(shí)候來(lái)顯示它。然而,我們從avcodec_decode_video()函數(shù)中得到的幀只是一個(gè)AVFrame,其中并沒(méi)有包含有用的PTS值(注意:AVFrameffmpegavcodec_decode_video()函數(shù)處理的包的DTS個(gè)信息。avcodec_decode_video()來(lái)計(jì)算出哪個(gè)包是一幀的第一個(gè)包。怎樣,avcodec_decode_video()將調(diào)用一個(gè)函數(shù)來(lái)為一幀申請(qǐng)一個(gè)緩沖。當(dāng)然,ffmpeg存的函數(shù)。所以我們制作了一個(gè)新的函數(shù)來(lái)保存一個(gè)包的時(shí)間戳。PTS視頻文件也不是完好的。所以,我們有三種選擇:同步音頻到視頻,同步視頻步視頻到音頻。們得到了解碼線程輸出到隊(duì)列中的包。這里我們需要的是從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)體難理解的變量中去。所以一開(kāi)始,這就是我們的函數(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)存釋放而且把指針設(shè)置為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-型來(lái)保存的。這個(gè)值是一個(gè)時(shí)間戳相當(dāng)于時(shí)間的度量,用來(lái)以流的time_base4224(這里并不完全正確)。我們可以通過(guò)除以幀率來(lái)把這個(gè)值轉(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í)間戳參數(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}將會(huì)算出實(shí)際的值。戳的時(shí)間。同時(shí),我們需要同步視頻到音頻。我們將設(shè)置一個(gè)音頻時(shí)間audio的函數(shù)get_audio_clock。一旦我們有了這個(gè)值,我們?cè)谝纛l和視頻失去同步的時(shí)候應(yīng)該做些什么呢?簡(jiǎn)單而有點(diǎn)笨的辦法是試著用跳過(guò)正確幀或者其它的影播放中所有的延時(shí)。換句話說(shuō),這個(gè)frame_timer就是指我們什么時(shí)候來(lái)顯后使用那個(gè)值來(lái)調(diào)度下一次刷新。這可能有點(diǎn)難以理解,所以請(qǐng)認(rèn)真研究代碼: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,}}們把最小的刷新值設(shè)置為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--指導(dǎo)6西沒(méi)處理。上次,我們掩飾了一點(diǎn)同步問(wèn)題,也就是同步音頻到視頻而不是其它的同步方式。我們將采用和視頻一樣的方式:做一個(gè)內(nèi)部視頻時(shí)鐘來(lái)記錄視播放時(shí)間的內(nèi)部值。開(kāi)始,你可能會(huì)想這和使用上一幀的時(shí)間戳來(lái)更新定時(shí)器當(dāng)前視頻時(shí)間值就是PTS_of_last_frame+(current_time-get_audio_clock中的方式很類似。和一個(gè)64位寬整型變量video_current_pts_time。時(shí)鐘更新將被放在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;}用來(lái)檢測(cè)av_sync_type變量然后決定調(diào)用get_audio_clock還是時(shí)鐘,這個(gè)函數(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=}通過(guò)丟棄樣本的方式來(lái)加速播放還是需要通過(guò)插值樣本的方式來(lái)放慢播放?synchronize_audio步,因?yàn)檫@樣會(huì)使同步音頻多于視頻包。所以我們?yōu)楹瘮?shù)synchronize_audio的長(zhǎng)度的均值。例如,第一次調(diào)用的時(shí)候,顯示出來(lái)我們失去同步的長(zhǎng)度為40ms,50ms最近的值比靠前的值要重要的多。所以我們將使用一個(gè)分?jǐn)?shù)系統(tǒng),叫c,然后用這樣的公式來(lái)計(jì)算差異:diff_sum=new_diff+diff_sum*c。當(dāng)我們準(zhǔn)備想要更多的信息,這里是一個(gè)解釋/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”部分來(lái)寫上代碼: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ù)。我們也可以設(shè)置一個(gè)范圍來(lái)限定我們一次進(jìn)行修正的但是如果我們想讓它變大,我們不能只是讓樣本大小變大,因?yàn)樵诰彌_區(qū)中沒(méi)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--以我們需要設(shè)置我們的主循環(huán)來(lái)捕捉鍵盤事件。然而當(dāng)我們捕捉到鍵盤事件后 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),}}為了檢測(cè)按鍵,我們先查了一下是否有SDL_KEYDOWN事件。然后我們使用get_master_clockseconds=frames*time_base(fps)。后面我們來(lái)看一下為什么要把這個(gè)值進(jìn)行一下轉(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;}}文件中標(biāo)記了一個(gè)叫做“seekstuff
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 四年級(jí)下冊(cè)語(yǔ)文期末復(fù)習(xí)專項(xiàng)練習(xí)
- 愛(ài)國(guó)旗小學(xué)生班會(huì)課件
- 高效領(lǐng)域自適應(yīng)故障診斷模型的研究
- 個(gè)性化心理護(hù)理對(duì)老年心腦血管患者管理效能及滿意度提升研究
- 設(shè)計(jì)綠色現(xiàn)代數(shù)智供應(yīng)鏈的數(shù)據(jù)管理體系:“一庫(kù)兩字典”的創(chuàng)新實(shí)踐
- 爆炸與安全課件
- 城市交通噪聲控制技術(shù)發(fā)展趨勢(shì)與設(shè)計(jì)要點(diǎn)研究
- 燃?xì)庑袠I(yè)職業(yè)病培訓(xùn)課件
- 網(wǎng)絡(luò)設(shè)備與網(wǎng)管系統(tǒng)施工管理策略
- 密碼管理師崗位面試問(wèn)題及答案
- 野外駐訓(xùn)安全注意事項(xiàng)
- 腦梗的病人護(hù)理疑難病例
- 墊片基礎(chǔ)知識(shí)培訓(xùn)課件
- 2025年第三屆藥膳大賽(選拔賽)理論知識(shí)考試題(附答案)
- 2024連續(xù)性腎替代治療下抗菌藥物劑量調(diào)整專家共識(shí)解析
- 課題申報(bào)參考:拔尖創(chuàng)新人才貫通式培養(yǎng)中的高中-大學(xué)銜接機(jī)制研究
- DB36-T 2070-2024 疼痛綜合評(píng)估規(guī)范
- 2024年05月陜西秦農(nóng)農(nóng)村商業(yè)銀行股份有限公司數(shù)字化及金融科技勞務(wù)派遣人員招考筆試歷年參考題庫(kù)附帶答案詳解
- 醫(yī)藥代表的臨床經(jīng)驗(yàn)分享
- 華中農(nóng)業(yè)大學(xué)《物聯(lián)網(wǎng)工程》2022-2023學(xué)年第一學(xué)期期末試卷
- 電信總經(jīng)理談服務(wù)
評(píng)論
0/150
提交評(píng)論