如何用FFmpeg API采集攝像頭視頻和麥克風音頻,並實現錄制文件的功能


之前一直用Directshow技術采集攝像頭數據,但是覺得涉及的細節比較多,要開發者比較了解Directshow的框架知識,學習起來有一點點難度。最近發現很多人問怎么用FFmpeg采集攝像頭圖像,事實上FFmpeg很早就支持通過DShow獲取采集設備(攝像頭、麥克風)的數據了,只是網上提供的例子比較少。如果能用FFmpeg實現采集、編碼和錄制(或推流),那整個實現方案就簡化很多,正因為這個原因,我想嘗試做一個FFmpeg采集攝像頭視頻和麥克風音頻的程序。經過一個星期的努力,終於做出來了。我打算把開發的心得和經驗分享給大家。我分三部分來講述:首先第一部分介紹如何用FFmpeg的官方工具(ffmpeg.exe)通過命令行來枚舉DShow設備和采集攝像頭圖像,這部分是基礎,能夠快速讓大家熟悉怎么用FFmpeg測試攝像頭采集;第二部分介紹我寫的采集程序的功能和用法;第三部分講解各個模塊包括采集、編碼、封裝和錄制是如何實現的。

1.用命令行枚舉采集設備和采集數據

  打開Cmd命令行控制台,進入FFmpeg的Bin目錄,輸入如下命令:

  ffmpeg -list_devices true -f dshow -i dummy  

  則在我的機器上顯示如下結果:

  在上面的命令行窗口中列出了兩個設備,一個是視頻采集設備,另外是一個音頻采集設備。另外,我們發現:音頻設備的名稱有亂碼,因為其中有中文名稱,后面在講到用API采集數據的時候會提到解決這個問題的方法。

  接着我們輸入另外一個命令行:

  ffmpeg -list_options true -f dshow -i video="USB 2861 Device"

  這個命令行的作用是獲取指定視頻采集設備支持的分辨率、幀率和像素格式等屬性,返回的是一個列表,結果如下:

  這里我們看到采集設備支持的最大分辨率是720x576,輸出像素格式是yuyv422,支持的幀率為29.97和25FPS。

  下面我們執行另外一條命令,將攝像頭的圖像和麥克風的音頻錄制保存成一個文件。命令如下:

  ffmpeg -f dshow -i video="USB 2861 Device" -f dshow -i audio="線路 (3- USB Audio Device)" -vcodec libx264 -acodec aac -strict -2 mycamera.mkv

  上面的命令行用video=指定視頻設備,用audio=指定音頻設備,后面的參數是定義編碼器的格式和屬性,輸出為一個名為mycamera.mkv的文件。

  命令運行之后,控制台打印FFmpeg的運行日志,按“Q”鍵則中止命令。

  這里有些讀者可能會問:采集設備不是支持多個分辨率嗎?怎么設置采集時用哪一種分辨率輸出?答案是用“-s”參數設置,若在上面的命令行加上“-s 720x576”,則FFmpeg就會以720x576的分辨率進行采集,如果不設置,則以默認的分辨率輸出。

  注意:如果你運行上面命令ffmpeg報如下錯誤:Could not run filter

  Video=XXX:Input/output error

  則說明該版本的ffmpeg不支持該采集設備。這是由於舊版本的FFmpeg一個Bug引起的,不支持需要連接crossbar連接的視頻采集設備(詳情可參考這個帖子:https://ffmpeg.zeranoe.com/forum/viewtopic.php?t=722)。新版本的FFmpeg(avdevice-58)修復了這個問題。所以如果你遇到這個問題,可以通過升級FFmpeg來解決。

  好,關於命令行的內容就介紹完了。

2.采集程序的使用

  這個程序叫“AVCapture”,能從視頻采集設備(攝像頭,采集卡)獲取圖像,支持圖像預覽;還可以采集麥克風音頻;支持對視頻和音頻編碼,支持錄制成文件。這是一個MFC開發的窗口程序,界面比較簡潔,如下圖:

   開始采集前需要選擇設備,點擊文件菜單的“打開設備”,彈出一個設備選擇對話框,如下圖所示:

  在對話框里選擇任意一個視頻設備和音頻設備,如果想啟用某種設備,必須勾選右邊的“啟用”選項,但如果只需要用其中一種采集設備,則可以把其中一個禁用掉。

  按“確定”則開始采集數據了。視頻和音頻會編碼后保存到一個文件中,這個文件的路徑是在配置文件中設置的,打開程序目錄下的Config.ini文件,則顯示如下字段:

[Client]

file_path = D:\camera.mkv

File_path就是錄制文件的路徑。

  采集的圖像默認顯示到中間的窗口中,如果不想預覽,可以在主菜單欄的“編輯”菜單中取消勾選“預覽視頻”。

3.功能模塊實現

    該采集程序實現了枚舉采集設備,采集控制、顯示圖像、視頻/音頻編碼和錄制的功能,其中輸入(Input)、輸出(Output)和顯示(Paint)這三個模塊分別用一個單獨的類進行封裝:CAVInputStream,CAVOutputStream,CImagePainter。CAVInputStream負責從采集設備獲取數據,提供接口獲取采集設備的屬性,以及提供回調函數把數據傳給上層。CAVOutputStream負責對采集的視頻和音頻流進行編碼、封裝,保存成一個文件。而CImagePainter則用來顯示圖像,使用了GDI繪圖,把圖像顯示到主界面的窗口。

3.1 枚舉采集設備

  采集前我們需要先選擇設備,把所有的設備名稱列出來,其中一個方法可以用第一節介紹的運行ffmpeg命令行工具來列舉,但是這樣有兩個問題:第一,假如設備名稱帶中文,則顯示的名稱有亂碼,因此,我們不知道它真實的名稱。第二,ffmpeg沒有API返回系統中安裝的采集設備列表,雖然FFmpeg提供了API把設備名稱列舉出來,但是是打印到控制台的,不是通過參數來返回,如下面這段代碼只能打印輸出結果到控制台。但是對於窗口界面程序,沒有控制台,怎么獲取命令行結果呢?

[cpp]  view plain  copy
 
  1.  AVFormatContext *pFmtCtx = avformat_alloc_context();  
  2.  AVDictionary* options = NULL;  
  3.  av_dict_set(&options, "list_devices", "true", 0);  
  4.  AVInputFormat *iformat = av_find_input_format("dshow");  
  5.  //printf("Device Info=============\n");  
  6.  avformat_open_input(&pFmtCtx, "video=dummy", iformat, &options);  
  7. //printf("========================\n");  

  我用了一種最傳統的做法來解決,就是通過Directshow的COM接口來枚舉設備,工程里面的EnumDevice接口就實現了枚舉設備的功能,函數原型如下:

[cpp]  view plain  copy
 
  1. //枚舉指定類型的所有采集設備的名稱  
  2. ENUMDEVICE_API HRESULT EnumDevice(CAPTURE_DEVICE_TYPE type, char * deviceList[], int nListLen, int & iNumCapDevices);  

   當然,如果讀者用的采集設備是固定一種,那么可以固定采集設備的名稱,這樣做可以省點事。 

3.2 注冊FFmpeg庫

[cpp]  view plain  copy
 
  1. av_register_all();   
  2. avdevice_register_all();  

  這兩個API可以在程序的構造函數和窗口初始化里面調用。

3.3 打開輸入設備

    首先需要指定采集設備的名稱。如果是視頻設備類型,則名稱以“video=”開頭;如果是音頻設備類型,則名稱以“audio=”開頭。調用avformat_open_input接口打開設備,將設備名稱作為參數傳進去,注意這個設備名稱需要轉成UTF-8編碼。然后調用avformat_find_stream_info獲取流的信息,得到視頻流或音頻流的索引號,之后會頻繁用到這個索引號來定位視頻和音頻的Stream信息。接着,調用avcodec_open2打開視頻解碼器或音頻解碼器,實際上,我們可以把設備也看成是一般的文件源,而文件一般采用某種封裝格式,要播放出來需要進行解復用,分離成裸流,然后對單獨的視頻流、音頻流進行解碼。雖然采集出來的圖像或音頻都是未編碼的,但是按照FFmpeg的常規處理流程,我們需要加上“解碼”這個步驟。

[cpp]  view plain  copy
 
  1.       int i;  
  2.       m_pInputFormat = av_find_input_format("dshow");  
  3.       ASSERT(m_pInputFormat != NULL);  
  4.   
  5. if(!m_video_device.empty())  
  6. {  
  7.     int res = 0;  
  8.   
  9.     string device_name = "video=" + m_video_device;  
  10.   
  11.     string device_name_utf8 = AnsiToUTF8(device_name.c_str(), device_name.length());  //轉成UTF-8,解決設備名稱包含中文字符出現亂碼的問題  
  12.   
  13.      //Set own video device's name  
  14.     if ((res = avformat_open_input(&m_pVidFmtCtx, device_name_utf8.c_str(), m_pInputFormat, &device_param)) != 0)  
  15.     {  
  16.         ATLTRACE("Couldn't open input video stream.(無法打開輸入流)\n");  
  17.         return false;  
  18.     }  
  19.     //input video initialize  
  20.     if (avformat_find_stream_info(m_pVidFmtCtx, NULL) < 0)  
  21.     {  
  22.         ATLTRACE("Couldn't find video stream information.(無法獲取流信息)\n");  
  23.         return false;  
  24.     }  
  25.     m_videoindex = -1;  
  26.     for (i = 0; i < m_pVidFmtCtx->nb_streams; i++)  
  27.     {  
  28.         if (m_pVidFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)  
  29.         {  
  30.             m_videoindex = i;  
  31.             break;  
  32.         }  
  33.     }  
  34.   
  35.     if (m_videoindex == -1)  
  36.     {  
  37.         ATLTRACE("Couldn't find a video stream.(沒有找到視頻流)\n");  
  38.         return false;  
  39.     }  
  40.     if (avcodec_open2(m_pVidFmtCtx->streams[m_videoindex]->codec, avcodec_find_decoder(m_pVidFmtCtx->streams[m_videoindex]->codec->codec_id), NULL) < 0)  
  41.     {  
  42.         ATLTRACE("Could not open video codec.(無法打開解碼器)\n");  
  43.         return false;  
  44.     }  
  45. }  
  46.   
  47.    //////////////////////////////////////////////////////////  
  48.   
  49. if(!m_audio_device.empty())  
  50. {  
  51.     string device_name = "audio=" + m_audio_device;  
  52.   
  53.     string device_name_utf8 = AnsiToUTF8(device_name.c_str(), device_name.length());  //轉成UTF-8,解決設備名稱包含中文字符出現亂碼的問題  
  54.   
  55.     //Set own audio device's name  
  56.     if (avformat_open_input(&m_pAudFmtCtx, device_name_utf8.c_str(), m_pInputFormat, &device_param) != 0){  
  57.   
  58.         ATLTRACE("Couldn't open input audio stream.(無法打開輸入流)\n");  
  59.         return false;  
  60.     }  
  61.   
  62.     //input audio initialize  
  63.     if (avformat_find_stream_info(m_pAudFmtCtx, NULL) < 0)  
  64.     {  
  65.         ATLTRACE("Couldn't find audio stream information.(無法獲取流信息)\n");  
  66.         return false;  
  67.     }  
  68.     m_audioindex = -1;  
  69.     for (i = 0; i < m_pAudFmtCtx->nb_streams; i++)  
  70.     {  
  71.         if (m_pAudFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)  
  72.         {  
  73.             m_audioindex = i;  
  74.             break;  
  75.         }  
  76.     }  
  77.     if (m_audioindex == -1)  
  78.     {  
  79.         ATLTRACE("Couldn't find a audio stream.(沒有找到音頻流)\n");  
  80.         return false;  
  81.     }  
  82.     if (avcodec_open2(m_pAudFmtCtx->streams[m_audioindex]->codec, avcodec_find_decoder(m_pAudFmtCtx->streams[m_audioindex]->codec->codec_id), NULL) < 0)  
  83.     {  
  84.         ATLTRACE("Could not open audio codec.(無法打開解碼器)\n");  
  85.         return false;  
  86.     }  
  87. }  

3.4 初始化輸出流

  前面我們已經初始化了InputStream,現在需要對OutputStream進行初始化,而要初始化輸出流需要知道視頻采集的分辨率,幀率,輸出像素格式等信息,還有音頻采集設備的采樣率,聲道數,Sample格式,而這些信息可通過CAVInputStream類的接口來獲取到。下面是初始化OutputStream的代碼:

[cpp]  view plain  copy
 
  1. m_InputStream.SetVideoCaptureCB(VideoCaptureCallback);  
  2. m_InputStream.SetAudioCaptureCB(AudioCaptureCallback);  
  3.   
  4. bool bRet;  
  5. bRet = m_InputStream.OpenInputStream(); //初始化采集設備  
  6. if(!bRet)  
  7. {  
  8.     MessageBox(_T("打開采集設備失敗"), _T("提示"), MB_OK|MB_ICONWARNING);  
  9.     return 1;  
  10. }  
  11.   
  12. int cx, cy, fps;  
  13. AVPixelFormat pixel_fmt;  
  14. if(m_InputStream.GetVideoInputInfo(cx, cy, fps, pixel_fmt)) //獲取視頻采集源的信息  
  15. {  
  16.     m_OutputStream.SetVideoCodecProp(AV_CODEC_ID_H264, fps, 500000, 100, cx, cy); //設置視頻編碼器屬性  
  17. }  
  18.   
  19.       int sample_rate = 0, channels = 0;  
  20. AVSampleFormat  sample_fmt;  
  21. if(m_InputStream.GetAudioInputInfo(sample_fmt, sample_rate, channels)) //獲取音頻采集源的信息  
  22. {  
  23.     m_OutputStream.SetAudioCodecProp(AV_CODEC_ID_AAC, sample_rate, channels, 32000); //設置音頻編碼器屬性  
  24. }  
  25.   
  26. //從Config.INI文件中讀取錄制文件路徑  
  27. P_GetProfileString(_T("Client"), "file_path", m_szFilePath, sizeof(m_szFilePath));  
  28.   
  29. bRet  = m_OutputStream.OpenOutputStream(m_szFilePath); //設置輸出路徑  
  30. if(!bRet)  
  31. {  
  32.     MessageBox(_T("初始化輸出失敗"), _T("提示"), MB_OK|MB_ICONWARNING);  
  33.     return 1;  
  34. }  

  在上面的代碼片段中,首先設置了視頻和音頻的數據回調函數。當采集開始時,視頻和音頻數據就會傳遞給相應的函數去處理,在該程序中,回調函數主要對圖像或音頻進行編碼,然后封裝成FFmpeg支持的容器(例如mkv/avi/mpg/ts/mp4)。另外,需要初始化OutputStream的VideoCodec和AudioCodec的屬性,在我的程序中,視頻編碼器是H264,音頻編碼器用AAC,通過CAVInputStream對象獲得輸入流的信息之后再賦值給輸出流相應的參數。最后調用m_OutputStream對象的OpenOutputStream成員函數打開編碼器和錄制的容器,其中我們需要傳入一個輸出文件路徑作為參數,這個為錄制的文件路徑,路徑是在Config.ini文件里配置的。如果OpenOutputStream函數返回true,則表示初始化輸出流成功。

3.5 讀取采集數據

 接着,我們就可以開始采集了。開始采集的函數實現如下:

[cpp]  view plain  copy
 
  1. bool  CAVInputStream::StartCapture()  
  2. {  
  3.     if (m_videoindex == -1 && m_audioindex == -1)  
  4.     {  
  5.         ATLTRACE("錯誤:你沒有打開設備\n");  
  6.         return false;  
  7.     }  
  8.   
  9.     m_start_time = av_gettime();  
  10.   
  11.     m_exit_thread = false;  
  12.   
  13.     if(!m_video_device.empty())  
  14.     {  
  15.         m_hCapVideoThread = CreateThread(  
  16.         NULL,                   // default security attributes  
  17.         0,                      // use default stack size    
  18.         CaptureVideoThreadFunc,       // thread function name  
  19.         this,          // argument to thread function   
  20.         0,                      // use default creation flags   
  21.         NULL);   // returns the thread identifier   
  22.     }  
  23.   
  24.     if(!m_audio_device.empty())  
  25.     {  
  26.         m_hCapAudioThread = CreateThread(  
  27.         NULL,                   // default security attributes  
  28.         0,                      // use default stack size    
  29.         CaptureAudioThreadFunc,       // thread function name  
  30.         this,          // argument to thread function   
  31.         0,                      // use default creation flags   
  32.         NULL);   // returns the thread identifier   
  33.     }  
  34.   
  35.     return true;  
  36. }  

   StartCapture函數分別建立了一個讀取視頻包和讀取音頻包的線程,兩個線程各自獨立工作,分別從視頻采集設備,音頻采集設備獲取到數據,然后進行后續的處理。(注意:兩個線程同時向一個文件寫數據可能會有同步的問題,FFmpeg內部可能沒有做多線程安全訪問的處理,所以最好在自己線程里加一個鎖進行互斥,從而保護臨界區的安全)

  其中,讀取攝像頭數據的線程的處理代碼如下:

[cpp]  view plain  copy
 
  1. DWORD WINAPI CAVInputStream::CaptureVideoThreadFunc(LPVOID lParam)  
  2. {  
  3.     CAVInputStream * pThis = (CAVInputStream*)lParam;  
  4.   
  5.     pThis->ReadVideoPackets();  
  6.   
  7.     return 0;  
  8. }  
  9.   
  10. int  CAVInputStream::ReadVideoPackets()  
  11. {  
  12.     if(dec_pkt == NULL)  
  13.     {  
  14.         ////prepare before decode and encode  
  15.         dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));  
  16.     }  
  17.   
  18.     int encode_video = 1;  
  19.     int ret;  
  20.   
  21.     //start decode and encode  
  22.   
  23.     while (encode_video)  
  24.     {  
  25.         if (m_exit_thread)  
  26.             break;  
  27.   
  28.         AVFrame * pframe = NULL;  
  29.         if ((ret = av_read_frame(m_pVidFmtCtx, dec_pkt)) >= 0)  
  30.         {  
  31.             pframe = av_frame_alloc();  
  32.             if (!pframe)   
  33.             {  
  34.                 ret = AVERROR(ENOMEM);  
  35.                 return ret;  
  36.             }  
  37.             int dec_got_frame = 0;  
  38.             ret = avcodec_decode_video2(m_pVidFmtCtx->streams[dec_pkt->stream_index]->codec, pframe, &dec_got_frame, dec_pkt);  
  39.             if (ret < 0)   
  40.             {  
  41.                 av_frame_free(&pframe);  
  42.                 av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");  
  43.                 break;  
  44.             }  
  45.             if (dec_got_frame)  
  46.             {  
  47.                 if(m_pVideoCBFunc)  
  48.                 {  
  49.                     CAutoLock lock(&m_WriteLock);  
  50.   
  51.                     m_pVideoCBFunc(m_pVidFmtCtx->streams[dec_pkt->stream_index], m_pVidFmtCtx->streams[m_videoindex]->codec->pix_fmt, pframe, av_gettime() - m_start_time);  
  52.                 }  
  53.   
  54.                 av_frame_free(&pframe);  
  55.             }  
  56.             else   
  57.             {  
  58.                 av_frame_free(&pframe);  
  59.             }  
  60.   
  61.             av_free_packet(dec_pkt);  
  62.         }  
  63.         else  
  64.         {  
  65.             if (ret == AVERROR_EOF)  
  66.                 encode_video = 0;  
  67.             else  
  68.             {  
  69.                 ATLTRACE("Could not read video frame\n");  
  70.                 break;  
  71.             }  
  72.         }  
  73.     }  
  74.   
  75.     return 0;  
  76. }  

  在CAVInputStream::ReadVideoPackets()函數中不停地調用 av_read_frame讀取采集到的圖像幀,接着調用avcodec_decode_video2進行“解碼”,這樣獲得了原始的圖像,圖像可能是RGB或YUV格式。解碼后的圖像通過m_pVideoCBFunc指向的回調函數回調給上層處理,回調函數里可進行后續的一些操作,比如對視頻幀編碼或直接顯示。

3.6 編碼、封裝成文件

  CAVInputStream的工作線程里面讀取到的視頻幀和音頻包通過回調函數傳給CAVOuputStream類去處理。下面是兩個回調函數的實現:

[cpp]  view plain  copy
 
  1. //采集到的視頻圖像回調  
  2. LRESULT CALLBACK VideoCaptureCallback(AVStream * input_st, enum PixelFormat pix_fmt, AVFrame *pframe, INT64 lTimeStamp)  
  3. {  
  4.     if(gpMainFrame->IsPreview())  
  5.     {  
  6.        gpMainFrame->m_Painter.Play(input_st, pframe);  
  7.     }  
  8.   
  9.     gpMainFrame->m_OutputStream.write_video_frame(input_st, pix_fmt, pframe, lTimeStamp);  
  10.     return 0;  
  11. }  
  12.   
  13. //采集到的音頻數據回調  
  14. LRESULT CALLBACK AudioCaptureCallback(AVStream * input_st, AVFrame *pframe, INT64 lTimeStamp)  
  15. {  
  16.     gpMainFrame->m_OutputStream.write_audio_frame(input_st, pframe, lTimeStamp);  
  17.     return 0;  
  18. }  

  視頻回調函數調用了CAVOutputStream的成員函數write_video_frame,這個函數對傳入的圖像幀進行編碼(H264),並且寫到指定的封裝文件;而音頻回調函數則調用了CAVOutputStream的另外一個成員函數write_audio_frame,這個函數負責對音頻編碼(AAC),然后輸出到指定的封裝文件。下面是Write_video_frame函數的實現代碼:

[cpp]  view plain  copy
 
  1. //input_st -- 輸入流的信息  
  2. //input_frame -- 輸入視頻幀的信息  
  3. //lTimeStamp -- 時間戳,時間單位為/1000000  
  4. //  
  5. int CAVOutputStream::write_video_frame(AVStream * input_st, enum PixelFormat pix_fmt, AVFrame *pframe, INT64 lTimeStamp)  
  6. {  
  7.     if(video_st == NULL)  
  8.        return -1;  
  9.   
  10.     //ATLTRACE("Video timestamp: %ld \n", lTimeStamp);  
  11.   
  12.    if(m_first_vid_time1 == -1)  
  13.    {  
  14.        TRACE("First Video timestamp: %ld \n", lTimeStamp);  
  15.        m_first_vid_time1 = lTimeStamp;  
  16.    }  
  17.   
  18.     AVRational time_base_q = { 1, AV_TIME_BASE };  
  19.   
  20.     if(img_convert_ctx == NULL)  
  21.     {  
  22.         //camera data may has a pix fmt of RGB or sth else,convert it to YUV420  
  23.         img_convert_ctx = sws_getContext(m_width, m_height,  
  24.             pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);  
  25.     }  
  26.   
  27.     sws_scale(img_convert_ctx, (const uint8_t* const*)pframe->data, pframe->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);  
  28.     pFrameYUV->width = pframe->width;  
  29.     pFrameYUV->height = pframe->height;  
  30.     pFrameYUV->format = PIX_FMT_YUV420P;  
  31.   
  32.     enc_pkt.data = NULL;  
  33.     enc_pkt.size = 0;  
  34.     av_init_packet(&enc_pkt);  
  35.   
  36.     int ret;  
  37.     int enc_got_frame = 0;  
  38.     ret = avcodec_encode_video2(pCodecCtx, &enc_pkt, pFrameYUV, &enc_got_frame);  
  39.   
  40.     if (enc_got_frame == 1)  
  41.     {  
  42.         //printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, enc_pkt.size);  
  43.          
  44.         if(m_first_vid_time2 == -1)  
  45.         {  
  46.             m_first_vid_time2 = lTimeStamp;  
  47.         }  
  48.   
  49.         enc_pkt.stream_index = video_st->index;                        
  50.       
  51.         //enc_pkt.pts= av_rescale_q(lTimeStamp, time_base_q, video_st->time_base);  
  52.         enc_pkt.pts = (INT64)video_st->time_base.den * lTimeStamp/AV_TIME_BASE;  
  53.   
  54.         m_vid_framecnt++;  
  55.           
  56.         ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);  
  57.         if(ret < 0)    
  58.         {  
  59.             char tmpErrString[128] = {0};  
  60.             ATLTRACE("Could not write video frame, error: %s\n", av_make_error_string(tmpErrString, AV_ERROR_MAX_STRING_SIZE, ret));  
  61.             av_packet_unref(&enc_pkt);  
  62.             return ret;  
  63.         }  
  64.   
  65.         av_free_packet(&enc_pkt);  
  66.     }  
  67.     else if(ret == 0)  
  68.     {  
  69.         ATLTRACE("Buffer video frame, timestamp: %I64d.\n", lTimeStamp); //編碼器緩沖幀  
  70.     }  
  71.   
  72.     return 0;  
  73. }  

   Write_video_frame和write_audio_frame是CAVOutputStream的兩個很重要的函數,其中對音頻包的處理略為復雜一些,主要是因為輸入的音頻和編碼后的音頻的frame_size不一樣,中間需要一個Fifo作緩沖隊列。另外時間戳PTS的計算也是很關鍵的,弄得不好保存的文件播放視音頻就不同步了,具體怎么實現你們看代碼吧。工程的代碼下載地址:點擊打開鏈接

 from:https://blog.csdn.net/zhoubotong2012/article/details/79338093


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM