ffmpeg教程(八)——vc用ffmpeg库开发播放器

做一个中文的开源播放器,用于视频的开发的教学。欢迎大家参与、交流。

如果你需要商业开发,请联系QQ:16614119

代码位置:https://code.csdn.net/kl222/player


1.1         在mingwvs中编译ffmpeg

Ffmpeg用mingw进行编译,并安装到/usr/local下。

$ ./configure--enable-libx264 --enable-gpl --extra-cflags=-I/usr/local/include--disable-iconv --extra-ldflags=-L/usr/local/lib --disable-stripping--enable-debug --disable-optimizations --enable-shared

$ make install


1.2         设置头文件

从/Mingw/include中复制头文件stdint.h、inttypes.h到/usr/local/include下。删掉这两个头文件中的#include<_mingw.h>

这两个头文件是C99标准中的类型定义。Vc不完全支持C99,所以没有此两文件。

 

1.3         引用库位置:

C:\MingW\lib\gcc\mingw32\4.8.1:libgcc位置

C:\MingW\msys\1.0\lib

C:\MingW\msys\1.0\local\lib:ffmpeg静态库位置

C:\MingW\msys\1.0\local\bin:ffmpeg动态库位置

Administrator@k-PC /usr/local/bin

$ ls *.lib

avcodec.lib  avfilter.lib  avutil.lib    swresample.lib

avdevice.lib avformat.lib  postproc.lib  swscale.lib

 

C:\Mingw\lib:mingw库位置

 

1.4   引用库:

/*静态库

#pragma comment( lib, "libavutil.a") 

#pragma comment( lib, "libavcodec.a") 

#pragma comment( lib, "libavformat.a") 

#pragma comment( lib, "libavdevice.a")

#pragma comment( lib, "libpostproc.a")

#pragma comment( lib, "libswresample.a")

#pragma comment( lib, "libswscale.a") 

//*/

 

//*动态库

#pragma comment( lib, "avformat.lib"

#pragma comment( lib, "avutil.lib")

#pragma comment( lib, "avcodec.lib")

#pragma comment( lib, "avdevice.lib")

#pragma comment( lib, "postproc.lib")

#pragma comment( lib, "swresample.lib")

#pragma comment( lib, "swscale.lib")

//*/

 

#pragma comment(lib,"libx264.a")

 

#pragma comment( lib, "libavicap32.a")

#pragma comment( lib, "libws2_32.a"

#pragma comment( lib, "libbz2.a"

#pragma comment( lib, "libz.a"

#pragma comment( lib, "libpsapi.a"

#pragma comment( lib, "libadvapi32.a"

#pragma comment( lib, "libshell32.a")

#pragma comment( lib, "libgcc.a")

#pragma comment( lib, "libmingwex.a")

 

把C:\MingW\msys\1.0\local\bin中的ffmpeg动态库复制到生成的执行文件相同的目录下。

Administrator@k-PC/usr/local/bin

$ ls *.dll

avcodec-55.dll*   avfilter-4.dll*   avutil-52.dll*    swresample-0.dll*

avdevice-55.dll*  avformat-55.dll*  postproc-52.dll*  swscale-2.dll*

 

1.5         设置ffmpeg库的日志

//设置日志的回调函数

void Log(void*, int,constchar*fmt,va_listvl)

{

     TRACE(fmt,vl);//打印到输出窗口

}

//在程序初始化时设置ffmpeg日志的回调函数

av_log_set_callback(Log);

 

1.6         初始化:

初始化中包括:打开视频文件、发现视频、音频信息、设置音视频编解码器、初始化显示窗口、初始化音频输出设备、并启动解码线程。

voidCplayerDoc::OnPlay()

{

    USES_CONVERSION;

 

    intnRet = 0;

 

    OnStop();

    Free();

   

    if(m_szFileName.IsEmpty())

    {

        MessageBox(NULL,_T("请打开播放文件"),_T("错误"),MB_OK);

        return;

    }

 

    if(m_FC)

        return;

 

    //打开媒体文件,并设置流,及相应的编译类型

    nRet=avformat_open_input(&m_FC,T2A(m_szFileName),NULL,NULL);

    if(nRet)

    {

        TRACE(_T("打开播放文件错误[%d]:%s\n"),nRet,m_szFileName);

        return;

    }

 

    //发现流信息,主要是那些没有文件头的流类型

    nRet=avformat_find_stream_info(m_FC,NULL);

    if(nRet)

    {

        return;

    }

 

    //输出流信息

    //av_dump_format(m_FC, -1, T2A(m_szFileName), 0);

 

    for(inti = 0; i <m_FC->nb_streams;i++)

    {

        //查找视频流索引,以后根据这个索引确定视频流

        if(AVMEDIA_TYPE_VIDEO == m_FC->streams[i]->codec->codec_type)

        {

            m_nVideoStream =i;

        }

 

        //查找音频流索引,以后根据这个索引确定音频流

        if(AVMEDIA_TYPE_AUDIO == m_FC->streams[i]->codec->codec_type)

        {

            m_nAudioStream =i;

        }

    }

 

    //==============================打开视频编解码器==========================

    if(-1!=m_nVideoStream)

    {

        //得到视频编解码器上下文件,这个在前面的过程建立

        m_vCodecContext =m_FC->streams[m_nVideoStream]->codec;

        if(NULL == m_vCodecContext)

        {

            TRACE(_T("NULL == m_vCodecContext\n"));

            return;

        }

       

        //根据id查找编解码器

        m_vCodec=avcodec_find_decoder(m_vCodecContext->codec_id);

        if(NULL == m_vCodec)

        {

            TRACE(_T("don't find video [%d]decoder\n",m_vCodecContext->codec_id));

            return;

        }

        //打开视频编解码器

        nRet=avcodec_open2(m_vCodecContext,m_vCodec,NULL);

        if(nRet)

        {

            TRACE(_T("open video codec false\n"));

            return;

        }

    }  

    //==============================打开视频编解码器==========================

 

    //==============================打开音频编解码器==========================

    if(-1!=m_nAudioStream)

    {

        m_aCodecContext =m_FC->streams[m_nAudioStream]->codec;

        if(NULL == m_aCodecContext)

        {

            TRACE(_T("NULL == m_aCodecContext\n"));

            return;

        }

        m_aCodec=avcodec_find_decoder(m_FC->streams[m_nAudioStream]->codec->codec_id);

        if(NULL == m_aCodec)

        {

            TRACE(_T("find audio codec [%d]fail\n"),m_FC->streams[m_nAudioStream]->codec->codec_id);

            return;

        }

        nRet=avcodec_open2(m_aCodecContext,m_aCodec,NULL);

        if(nRet)

        {

            TRACE(_T("open audio codec false\n"));

            return;

        }

    }

    //==============================打开音频编解码器==========================

 

    //初始化音频输出设备

    m_WavePlay.Init(m_aCodecContext->sample_rate,m_aCodecContext->sample_fmt,m_aCodecContext->channels);

 

    //调整窗口到屏幕中间,窗口大小为视频大小

    CMainFrame*pView = (CMainFrame*)(AfxGetMainWnd()); //得到主窗口指针

    //把窗口大小设置为视频大小

    CRectrect(0, 0,

        m_vCodecContext->width,m_vCodecContext->height);//视频大小

    pView->ClientToScreen(&rect);                       //把客户区坐标转化为屏幕坐标

    //把窗口坐标调整到屏幕中心

    intnScreenWidth =GetSystemMetrics(SM_CXSCREEN);  //屏幕宽度   

    intnScreenHeight =GetSystemMetrics(SM_CYSCREEN);  //屏幕高度

    intnLeft = (nScreenWidth - rect.Width()) / 2;      

    intnTop = (nScreenHeight - rect.Height()) / 2;

    CRectrectWindows(nLeft,nTop,nLeft +rect.Width(),nTop +rect.Height());

    //设置窗口大小

    pView->SetWindowPos(NULL,rectWindows.left,rectWindows.top,

        rectWindows.Width(),rectWindows.Height(),NULL);

   

    m_bExit=FALSE;

    DWORDid;

    //启动解码线程

    CreateThread(NULL, 0, ThreadRead,this, 0, &id);

}

 

1.7         从流中读入一个包(AVPact)过程

int CplayerDoc::OnReadFreame()

{

     intnRet = 0;

     AVPacketpkt;

     intframeFinished = 0;

 

     //初始化包结构

     av_init_packet(&pkt);

     while(!m_bExit && 0 ==av_read_frame(m_FC, &pkt))//从流中读入一个包

     {

         //根据流索引判断流的类型,并进行相应的解码处理

         if(pkt.stream_index==m_nVideoStream)//是视频流

         {

              //对视频流数据进行解码

              nRet = OnProcessVideo(&pkt);

         }

         elseif(pkt.stream_index ==m_nAudioStream)//是音频流

         {

              //对音频流数据进行解码

              nRet = OnProcessAudio(&pkt);

         }

 

         //释放包的资源

         av_free_packet(&pkt);

     }

 

     returnnRet;

}

 

1.8   视频解码

    视频解码是把解复合的包(AVPacket)由解码器解出图像帧(AVFrame)。AVFrame::format指示图像的格式。AVFrame::data包含图像数据。它是一个多维数组。例如:RGB位图数据包含在AVFrame::data[0],RGB位图数据长度包含在AVFrame::linesize[0]。YVU位图包每个分量数据占用AVFrame::data[3]每个数组,每个分量数据的长度分别包含在AVFrame::linesize[3]。

int CplayerDoc::OnProcessVideo(AVPacket *pPkt)

{

     intnRet = 0;

     intframeFinished = 0;

     AVFrame*pFrame =av_frame_alloc();//分配一帧数据结构

 

     //解码

     nRet= avcodec_decode_video2(m_vCodecContext, pFrame,&frameFinished, pPkt);

     if(frameFinished)//不为0,则解码成功,被解码的帧已放入到pFrame中.为0,解码不成功,因为数据不完全,编解码器会缓存已处理过的数据,下次调用时会继续解码直到成功

     {

         TRACE(_T("format:%d\n"),pFrame->format);//此帧的格式 enum AVPixelFormat

         if(pFrame->key_frame)//是否是关键帧

         {

              TRACE(_T("key frame.pts:%lld,pkt_pts%lld,pkt_dts:%lld\n"),

                   pFrame->pts,pFrame->pkt_pts,pFrame->pkt_dts);

         }

         else

              TRACE(_T("no keyframe.pts:%lld,pkt_pts%lld,pkt_dts:%lld\n"),

              pFrame->pts,pFrame->pkt_pts,pFrame->pkt_dts);

 

         AVPicture*pPic =newAVPicture;//在CplayerView::Display(CDC*pDc, int nWidth, int nHeight, AVPicture* pPic)中释放

         //把帧格式转化为RGB24格式

         AVFrameToRGB24CBitmap(pFrame,m_vCodecContext->width,m_vCodecContext->height,pPic);

 

         //把RGB24位图放到播放缓存,传给视图使用

         m_mutex.Lock();

         m_Bitmap.push_back(pPic);

         m_mutex.Unlock();

 

         //向视频通知有新帧产生,做好定时后,这部分可以不用

         CMainFrame*pMain=(CMainFrame*)AfxGetApp()->m_pMainWnd;

         CplayerView*pView=(CplayerView*)pMain->GetActiveView();

         ::PostMessage(pView->GetSafeHwnd(),WM_USER_THREADEND,m_vCodecContext->width,m_vCodecContext->height);

     }

 

     //释放帧的资源

     av_frame_free(&pFrame);

     returnnRet;

}

1.9         把帧格式转化为RGB24格式

不同的操作系统的绘图函数只支持指定的位图格式。例如:GDI绘图只支持RGB的位图,所以需要进行位图格式转换。这里我们转换成RGB24位图。在实际生产代码中,还需要检查设备支持的位图格式。由于GDI支持RGB24位图,其设备兼容格式由GDI库进行处理,所以,我们这里转换成RGB24。

 

intCplayerDoc::AVFrameToRGB24CBitmap(AVFrame*frame,AVPicture*pPic)

{

    intnRet= 0;

    structSwsContext*pSwsCtx =NULL;

 

    if(NULL == frame)

    {

        return-1;

    }

 

    if(PIX_FMT_BGR24 == frame->format)

    {

        memcpy(pPic->data[0],frame->data[0],frame->linesize[0]);

        pPic->linesize[0]=frame->linesize[0];

        return0;

    }

 

    //为图片分配置空间

    nRet=avpicture_alloc(pPic,PIX_FMT_BGR24,frame->width,frame->height);

    if(nRet)

        return-2;

 

    //得到窗口客户区域大小

    CRectrect(0, 0,frame->width,frame->height);

    CMainFrame*pMain = (CMainFrame *)AfxGetApp()->m_pMainWnd;//得到主窗口指针

    if(pMain)

    {

        //得到当前活动视图指针

        CplayerView*pView = (CplayerView*)pMain->GetActiveView();

        if(pView->GetSafeHwnd())

        {

            pView->GetClientRect(&rect);

            TRACE(_T("client:w:%d;h:%d\n"),rect.Width(),rect.Height());

        }

    }

   

    //设置图像转换上下文 

    pSwsCtx=sws_getCachedContext (NULL,

        frame->width,               //源宽度

        frame->height,              //源高度

        (AVPixelFormat)frame->format,//源格式

        rect.Width(),               //目标宽度

        rect.Height(),              //目标高度

        PIX_FMT_BGR24,              //目的格式

        SWS_FAST_BILINEAR,          //转换算法

        NULL,NULL,NULL);  

    if(NULL == pSwsCtx)

    {

        TRACE(_T("sws_getContext false\n"));

        avpicture_free(pPic);

        return-3;

    }

 

    //进行图片转换

    sws_scale(pSwsCtx,

        frame->data,frame->linesize,

        0, frame->height,

        pPic->data,pPic->linesize);

 

    sws_freeContext(pSwsCtx);

 

    returnnRet;

}

1.10      显示一视频帧

 

int CplayerView::Display(CDC*pDc,intnWidth,intnHeight,AVPicture*pPic)

{

     intnRet = 0;

     BITMAPINFObmi = {0};

     bmi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);//BITMAPINFO结构大小

     bmi.bmiHeader.biBitCount= 24;//24

     bmi.bmiHeader.biCompression=BI_RGB;//未压缩的RGB

     bmi.bmiHeader.biHeight= -nHeight;//图片宽,原始图形是倒的,所以这里用负号进行翻转

     bmi.bmiHeader.biWidth=nWidth;//图片高

     bmi.bmiHeader.biPlanes= 1;//调色板,因为是彩色的,所以设置为 

     //bmi.bmiHeader.biSizeImage= pPic->linesize[0];//图片的大小可以不用

 

     CRectrect;

     GetClientRect(rect);

     //把DIB位图画到设备上显示出来

     StretchDIBits(pDc->GetSafeHdc(),//设备上下文句柄

         0,0, rect.Width(), rect.Height(), //设备dc的大小

         0, 0, nWidth, nHeight,//图片的大小

         pPic->data[0],//RGB24位图数据

         &bmi, //BITMAPINFO结构指针,用于描述位图

         DIB_RGB_COLORS,//RGB格式

         SRCCOPY);//操作运算,复制

 

     //释放在CplayerDoc::OnProcessVideo()中分配的内存

     avpicture_free(pPic);

     deletepPic;

 

     returnnRet;

}

 

1.11      调整显示窗口

    //调整窗口到屏幕中间,窗口大小为视频大小

    CMainFrame*pView = (CMainFrame*)(AfxGetMainWnd()); //得到主窗口指针

    //把窗口大小设置为视频大小

    CRectrect(0, 0,

        m_vCodecContext->width,m_vCodecContext->height);//视频大小

    pView->ClientToScreen(&rect);                       //把客户区坐标转化为屏幕坐标

    //把窗口坐标调整到屏幕中心

    intnScreenWidth =GetSystemMetrics(SM_CXSCREEN);  //屏幕宽度   

    intnScreenHeight =GetSystemMetrics(SM_CYSCREEN);  //屏幕高度

    intnLeft = (nScreenWidth - rect.Width()) / 2;      

    intnTop = (nScreenHeight - rect.Height()) / 2;

    CRectrectWindows(nLeft,nTop,nLeft +rect.Width(),nTop +rect.Height());

    //设置窗口大小

    pView->SetWindowPos(NULL,rectWindows.left,rectWindows.top,

        rectWindows.Width(),rectWindows.Height(),NULL);

 

1.12      解码音频帧

intCplayerDoc::OnProcessAudio(AVPacket *pPkt)

{

    intnRet= 0;

    intframeFinished= 0;

    AVFrame *pFrame = av_frame_alloc();//分配一帧数据结构

 

    //解码

    nRet=avcodec_decode_audio4(m_aCodecContext,pFrame,&frameFinished,pPkt);

    if(frameFinished)//不为0,则解码成功,被解码的帧已放入到pFrame.0,解码不成功,因为数据不完全,编解码器会缓存已处理过的数据,下次调用时会继续解码直到成功

    {

        TRACE(_T("format:%d\n"),pFrame->format);//此帧音频的采样格式 enum AVSampleFormat

        if(pFrame->key_frame)//是否是关键帧

        {

            TRACE(_T("key frame.pts:%lld,pkt_pts%lld,pkt_dts:%lld\n"),

                pFrame->pts,pFrame->pkt_pts,pFrame->pkt_dts);

        }

        else

            TRACE(_T("no key frame.pts:%lld,pkt_pts%lld,pkt_dts:%lld\n"),

            pFrame->pts,pFrame->pkt_pts,pFrame->pkt_dts);

 

        nRet=m_WavePlay.AddFrame(pFrame);

           

    }

    TRACE(_T("OnProcessAudio\n"));

    //释放帧的资源

    av_frame_free(&pFrame);

    returnnRet;

}

 

1.13      声音播放waveOut API说明

//声明:向波形输出设备发送一条消息
waveOutMessage(
  hWaveOut: HWAVEOUT; {设备句柄}
  uMessage: UINT;     {消息}
  dw1: DWORD          {消息参数}
  dw2: DWORD          {消息参数}
): Longint;           {将由设备给返回值}

 

//声明:打开waveout设备

waveOutOpen(

  lphWaveOut: PHWaveOut;   {用于返回设备句柄的指针;如果dwFlags=WAVE_FORMAT_QUERY, 这里应是 nil}

  uDeviceID: UINT;         {设备ID;可以指定为:WAVE_MAPPER, 这样函数会根据给定的波形格式选择合适的设备}

  lpFormat: PWaveFormatEx; {TWaveFormat结构的指针; TWaveFormat包含要申请的波形格式}

  dwCallback: DWORD        {回调函数地址或窗口句柄;若不使用回调机制, 设为 nil}

  dwInstance: DWORD        {给回调函数的实例数据;不用于窗口}

  dwFlags: DWORD          {打开选项}

): MMRESULT;              {成功返回 0;可能的错误值见下:}

 

MMSYSERR_BADDEVICEID = 2{设备ID超界}

MMSYSERR_ALLOCATED  = 4{指定的资源已被分配}

MMSYSERR_NODRIVER   = 6 {没有安装驱动程序}

MMSYSERR_NOMEM     = 7 {不能分配或锁定内存}

WAVERR_BADFORMAT   = 32;{设备不支持请求的波形格式}

 

//TWaveFormatEx 结构:

TWaveFormatEx = packedrecord

  wFormatTag: Word;       {指定格式类型;默认WAVE_FORMAT_PCM = 1;}

  nChannels: Word;        {指出波形数据的通道数;单声道为 1, 立体声为 2}

  nSamplesPerSec: DWORD;  {指定样本速率(每秒的样本数)}

  nAvgBytesPerSec: DWORD; {指定数据传输的平均速率(每秒的字节数)}

  nBlockAlign: Word;      {指定块对齐(单位字节), 块对齐是数据的最小单位}

  wBitsPerSample: Word;   {采样大小(字节)}

  cbSize: Word;          {附加信息大小; PCM格式没这个字段}

end;

{16 位立体声 PCM的块对齐是 4 字节(每个样本2字节, 2个通道)}

 

//打开选项 dwFlags的可选值:

WAVE_FORMAT_QUERY = $0001;    {只是判断设备是否支持给定的格式, 并不打开}

WAVE_ALLOWSYNC   = $0002;    {当是同步设备时必须指定}

CALLBACK_WINDOW  = $00010000;{当 dwCallback是窗口句柄时指定}

CALLBACK_FUNCTION = $00030000;{当 dwCallback是函数指针时指定}

 

//如果选择窗口接受回调信息,可能会发送到窗口的消息有:

MM_WOM_OPEN  = $3BB;

MM_WOM_CLOSE  = $3BC;

MM_WOM_DONE  = $3BD;

 

//如果选择函数接受回调信息,可能会发送给函数的消息有:

WOM_OPEN  = MM_WOM_OPEN;

WOM_CLOSE = MM_WOM_CLOSE;

WOM_DONE  = MM_WOM_DONE;

 

 

//声明:准备一个波形数据块用于播放。提示:必须调用 GlobalAlloc 给TWaveHdr 和其中的 lpData 指向的缓冲区分配内存(使用 GMEM_MOVEABLE、GMEM_SHARE), 并用 GlobalLock 锁定.

waveOutPrepareHeader(

  hWaveOut: HWAVEOUT;     {设备句柄}

  lpWaveOutHdr: PWaveHdr; {TWaveHdr结构的指针}

  uSize: UINT            {TWaveHdr结构大小}  

): MMRESULT;             {成功返回 0;可能的错误值见下:}

 

MMSYSERR_INVALHANDLE = 5{设备句柄无效}

MMSYSERR_NOMEM       = 7 {不能分配或锁定内存}

MMSYSERR_HANDLEBUSY  = 12;{其他线程正在使用该设备}

 

//TWaveHdr 是 wavehdr_tag结构的重定义

wavehdr_tag = record

  lpData: PChar;          {指向波形数据缓冲区}

  dwBufferLength: DWORD;  {波形数据缓冲区的长度}

  dwBytesRecorded: DWORD; {若首部用于输入,指出缓冲区中的数据量}

  dwUser: DWORD;          {指定用户的32位数据}

  dwFlags: DWORD;         {缓冲区标志}

  dwLoops: DWORD;         {循环播放次数,仅用于输出缓冲区}

  lpNext: PWaveHdr;       {保留}

  reserved: DWORD;        {保留}

end;

 

//TWaveHdr 中的 dwFlags的可选值:

WHDR_DONE      = $00000001;{设备已使用完缓冲区,并返回给程序}

WHDR_PREPARED  = $00000002;{waveInPrepareHeader或 waveOutPrepareHeader已将缓冲区准备好}

WHDR_BEGINLOOP = $00000004;{缓冲区是循环中的第一个缓冲区,仅用于输出}

WHDR_ENDLOOP  = $00000008; {缓冲区是循环中的最后一个缓冲区, 仅用于输出}

WHDR_INQUEUE  = $00000010; {reserved for driver }

 

 

//声明:清除由waveOutPrepareHeader完成的准备

//提示:
//
设备使用完数据块后, 须调用此函数;
//
释放(GlobalFree)缓冲区前,须调用此函数;
//
取消一个尚未准备的缓冲区将无效,但函数返回 0

waveOutUnprepareHeader(
  hWaveOut: HWAVEOUT;     {设备句柄}
  lpWaveOutHdr: PWaveHdr; {TWaveHdr 结构的指针}
  uSize: UINT             {TWaveHdr 结构大小}
): MMRESULT;              {成功返回 0; 可能的错误值见下:}
 
MMSYSERR_INVALHANDLE = 5{设备句柄无效}
MMSYSERR_HANDLEBUSY  = 12; {设备已被另一线程使用}
WAVERR_STILLPLAYING  = 33; {缓冲区还在队列中}
 
//TWaveHdr 是 wavehdr_tag 结构的重定义
wavehdr_tag = record
  lpData: PChar;          {指向波形数据缓冲区}
  dwBufferLength: DWORD;  {波形数据缓冲区的长度}
  dwBytesRecorded: DWORD; {若首部用于输入, 指出缓冲区中的数据量}
  dwUser: DWORD;          {指定用户的32位数据}
  dwFlags: DWORD;         {缓冲区标志}
  dwLoops: DWORD;         {循环播放次数, 仅用于输出缓冲区}
  lpNext: PWaveHdr;       {保留}
  reserved: DWORD;        {保留}
end;
 
//TWaveHdr 中的 dwFlags 的可选值:
WHDR_DONE      = $00000001; {设备已使用完缓冲区, 并返回给程序}
WHDR_PREPARED  = $00000002; {waveInPrepareHeader 或 waveOutPrepareHeader 已将缓冲区准备好}
WHDR_BEGINLOOP = $00000004; {缓冲区是循环中的第一个缓冲区, 仅用于输出}
WHDR_ENDLOOP   = $00000008; {缓冲区是循环中的最后一个缓冲区, 仅用于输出}
WHDR_INQUEUE   = $00000010; { reserved for driver }

 

//声明: 向输出设备发送一个数据块

//提示: 把数据缓冲区传给waveOutWrite 之前, 必须使用waveOutPrepareHeader准备该缓冲区;

//若未调用 waveOutPause函数暂停设备, 则第一次把数据块发送给设备时即开始播放.

 

waveOutWrite(

  hWaveOut: HWAVEOUT;     {设备句柄}

  lpWaveOutHdr: PWaveHdr; {TWaveHdr结构的指针}

  uSize: UINT            {TWaveHdr结构大小}

): MMRESULT;             {成功返回 0;可能的错误值见下:}

 

MMSYSERR_INVALHANDLE = 5{设备句柄无效}

MMSYSERR_HANDLEBUSY  = 12;{设备已被另一线程使用}

WAVERR_UNPREPARED    = 34;{未准备数据块}

 

//TWaveHdr 是 wavehdr_tag结构的重定义

wavehdr_tag = record

  lpData: PChar;          {指向波形数据缓冲区}

  dwBufferLength: DWORD;  {波形数据缓冲区的长度}

  dwBytesRecorded: DWORD; {若首部用于输入,指出缓冲区中的数据量}

  dwUser: DWORD;          {指定用户的32位数据}

  dwFlags: DWORD;         {缓冲区标志}

  dwLoops: DWORD;         {循环播放次数,仅用于输出缓冲区}

  lpNext: PWaveHdr;       {保留}

  reserved: DWORD;        {保留}

end;

 

//TWaveHdr 中的 dwFlags的可选值:

WHDR_DONE      = $00000001;{设备已使用完缓冲区,并返回给程序}

WHDR_PREPARED  = $00000002;{waveInPrepareHeader或 waveOutPrepareHeader已将缓冲区准备好}

WHDR_BEGINLOOP = $00000004;{缓冲区是循环中的第一个缓冲区,仅用于输出}

WHDR_ENDLOOP  = $00000008; {缓冲区是循环中的最后一个缓冲区, 仅用于输出}

WHDR_INQUEUE  = $00000010; {reserved for driver }

 

1.14      初始化音频设备

 

intCWavePlay::Init(DWORDnSampleRate,enumAVSampleFormatnSampleFormat,WORDnChannels)

{

    WAVEFORMATEXwfx ={0};             /*look this up in your documentation */

    MMRESULTresult =MMSYSERR_NOERROR/*for waveOut return values */

   

    /*

    * first weneed to set up the WAVEFORMATEX structure.

    * thestructure describes the format of the audio.

    */

    wfx.nSamplesPerSec=nSampleRate;/*采样率 */

    wfx.wBitsPerSample=av_get_bytes_per_sample(nSampleFormat);//采样大小,ffmpeg叫采样格式,enum AVSampleFormat常量,因为windows waveout api只支持816

    wfx.nChannels=nChannels;/*通道数*/

    /*

    *WAVEFORMATEX also has other fields which need filling.

    * as longas the three fields above are filled this should

    * work forany PCM (pulse code modulation) format.

    */

    wfx.cbSize= 0; /* size of _extra_ info */

    wfx.wFormatTag=WAVE_FORMAT_PCM;

    wfx.nBlockAlign= (wfx.wBitsPerSample*wfx.nChannels)>> 3;

    wfx.nAvgBytesPerSec=wfx.nBlockAlign*wfx.nSamplesPerSec;

 

    //检查格式是否被支持

    result=waveOutOpen(

        NULL,                //ptr can be NULL for query

        WAVE_MAPPER,           //the device identifier

        &wfx,                //defines requested format

        NULL,                //no callback

        NULL,                // no instance data

        WAVE_FORMAT_QUERY); // query only, do not open device

    if(WAVERR_BADFORMAT == result)

    {

        //设置默认格式

        m_bConvertFormat=TRUE;//需要转换格式

        wfx.nSamplesPerSec= 44100; /* 采样率*/

        wfx.wBitsPerSample= 16;//采样大小,ffmpeg叫采样格式,enum AVSampleFormat常量,因为windows waveout api只支持816

        wfx.nChannels= 2; /*通道数*/

        wfx.cbSize= 0; /* size of _extra_ info */

        wfx.wFormatTag=WAVE_FORMAT_PCM;

        wfx.nBlockAlign= (wfx.wBitsPerSample*wfx.nChannels)>> 3;

        wfx.nAvgBytesPerSec=wfx.nBlockAlign*wfx.nSamplesPerSec;

    }

 

    StartThread();

    //设置线程处理响应事件

    result=waveOutOpen(&m_hWaveOut,WAVE_MAPPER, &wfx,

        (DWORD_PTR)m_ThreadID, (DWORD_PTR)this,CALLBACK_THREAD);

    /*设置回调函数,响应事件

    * try toopen the default wave device. WAVE_MAPPER is

    * aconstant defined in mmsystem.h, it always points to the

    * defaultwave device on the system (some people have 2 or

    * moresound cards).

    */

    //result= waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &wfx,

    // (DWORD_PTR)CallBackwaveOut, (DWORD_PTR)this,CALLBACK_FUNCTION);

    if(result!=MMSYSERR_NOERROR)

    {

        TRACE(_T("unable to open WAVE_MAPPER device:%d\n"),result);

        returnresult;

    }

 

    returnresult;

}

 

1.15      声音格式转换

//音频数据转换成默认的格式

intCWavePlay::AVFrameToAudio(AVFrame*frame,char**pBuf,int&len)

{

    intnRet= 0;

    structSwrContext *pSC =NULL;

 

    //准备格式转换

    pSC=swr_alloc_set_opts(NULL,

        AV_CH_LAYOUT_STEREO,              //输出通道布局

        AVSampleFormat::AV_SAMPLE_FMT_S16,//输出格式

        44100,                            //输出采样率

        AV_CH_LAYOUT_STEREO,              //输入通道布局

        (AVSampleFormat)frame->format,    //输入格式

        frame->sample_rate,               //输入采样率

        NULL,NULL);

    if(NULL == pSC)

        return-1;

 

    nRet=swr_init(pSC);

    if(nRet)

    {

        TRACE(_T("swr init fail\n"));

        returnnRet;

    }

 

    //转换音频

    nRet=swr_convert(pSC,

        (uint8_t**)pBuf,//输出BUFFER

        len/frame->channels/av_get_bytes_per_sample(AV_SAMPLE_FMT_S16),//buffer长度能容纳每个通道多少个采样,输出缓冲器中每个通道的采样数

        (constuint8_t**)frame->data,

        frame->nb_samples//每个通道的采样数

        );

    if(nRet> 0)

    {

        len=nRet *av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) *frame->channels;

        nRet= 0;

    }

    swr_free(&pSC);

    returnnRet;

}

 

1.16  把音频数据写到音频设备

intCWavePlay::AddFrame(AVFrame *pFrame)

{

    intnRet= 0;

    if(NULL == pFrame)

        return-1;

 

    //检查是否超出缓存

    if(m_nBufferNumber>= 5)

    {

        return-100;

    }

   

    intnLen=pFrame->linesize[0]*pFrame->channels<< 1;

    char*pBuf=newchar[nLen];

    if(m_bConvertFormat)

    {

        //转换音频格式

        AVFrameToAudio(pFrame, &pBuf,nLen);

    }

    else

    {

        nLen=pFrame->linesize[0]*pFrame->channels;

        memcpy(pBuf,pFrame->data,nLen);

    }

   

    writeAudioBlock(m_hWaveOut,pBuf,nLen);

   

    InterlockedIncrement(&m_nBufferNumber);

    returnnRet;

}

 

 

voidCWavePlay::writeAudioBlock(HWAVEOUThWaveOut,char*block,DWORDsize)

{

    WAVEHDR*pHeader =newWAVEHDR;

    MMRESULTresult;

 

    //用音频数据初始化 WAVEHDR结构

    memset(pHeader, 0, sizeof(WAVEHDR));

    pHeader->dwBufferLength=size;

    pHeader->lpData= (LPSTR)block;

    //pHeader->dwFlags= WHDR_DONE;

 

    //为播放准备数据块

    result=waveOutPrepareHeader(hWaveOut,pHeader,sizeof(WAVEHDR));

    if(result!=MMSYSERR_NOERROR )

    {

        TRACE(_T("waveOutPrepareHeader fail:%d\n", result));

        return;

    }

   

    //写入数据块到设备.一般情况下waveOutWrite立即返回。除了设备使用同步的情况。

    result=waveOutWrite(hWaveOut,pHeader,sizeof(WAVEHDR));

    if(result!=MMSYSERR_NOERROR )

    {

        TRACE(_T("waveOutPrepareHeader fail:%d\n", result));

        return;

    }

}

 


你可能感兴趣的:(ffmpeg教程(八)——vc用ffmpeg库开发播放器)