从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用

原文地址:http://blog.yundiantech.com/?log=blog&id=10

======================================================

前面介绍了使用FFMPEG+Qt解码视频并显示。

现在我们就着手给它加上声音播放。

播放声音有很多种方式:

以windows系统为例,可以使用如下方法播放音频:

1.直接调用系统API的wavein、waveout等函数

2.使用directsound播放

 

这些方法都只能在windows上使用,且相当难用(至少我这么觉得),这个对于新手来说要把它们用好并稳定运行比较难。

 

想想我们使用FFMPEG的经验,理论上应该也有现成的库用来播放音频的,百度了下,基本上有下面几个主流的音频开源库:

 

1:OpenAL:这个库比较好,强大,跨平台,不过这个库的资料相对比较少。LGPL;

 

2:PortAudio:这个库也很不错,接口简单,方便获取 设备,播放音频。没有看到硬件混音接口,或许多开几个播放接口就可以实现。GPL,但是可以不开源自己的程序,其官方网站是这么写的,除非是我理解错了。可以登录其官方网站查看版权。

 

3:SDL:很有名的跨平台库,可惜音频方面,目前不支持采集音频设备,不过SDL2.0中已经保留接口了,应该以后会实现,不知道要到那个版本了。

 

4:SDL_audioin:如果你留心,应该可以找到这个库,应为SDL不支持硬件设备音频采集,这个可以获取设备声音。虽然跨平台,可是不支持 苹果系统、iso当然也不支持,具体                   


 

其中OpenAL、ProtAudio和SDL这三个库 我都有用过,觉得还是SDL比较好用,因此我们使用SDL来播放音频。

使用SDL的好处:

1.网上资料多,学习起来方便

2.跨平台,Windows、Linux、Android、IOS通吃,基本上这4个系统就够我们用了。

   意味着我们可以使用相同的代码在这些系统上直接运行。

3.库体积相对比较小

而且还有很多的小游戏也是在SDL上进行开发的,多如小时候玩的那些游戏,使用贴图移动完成,包括:飞机大战,打地鼠,三国的小游戏均可以写出来,所以对SDL感兴趣的小伙伴可以去研究下。后面有空的话,我也会把在Linux下配置SDL的方法整理出来。

SDL有一个缺点就是不能够采集音频,但是像安卓、IOS这样的系统,在采集音频的时候还是推荐直接用他们的API采集,因此我们完全可以容忍SDL不能采集音频这个缺点。

并且FFMPEG是支持采集音频的,必要的时候我们也可以直接使用FFMPEG采集。

 

SDL本身是一个多媒体库,其最强大的地方不是在音频上,而是在图形图形上。

那么,为何我们前面不使用SDL显示视频呢?

 

在使用SDL的过程中,我发现,将SDL嵌入我们的窗体之后,窗体上方的控件样式会失效(比如QSS样式)(目前我们是解析视屏后,把图片帧直接刷到显示界面上的),这就意味着,放在窗体上方的按钮或者其他控件不能够透明。而我们的播放器当然需要在播放界面上放一个带透明度的控制栏啦。而且目前也找不到好的解决方法,无奈之下,只好将图像显示改成直接用绘图的方法绘制在控件上。当然了,直接绘制的方法在效率上也不会差。因此就这么干了。

 

所以我们只是使用SDL用来播放音频,也算是大材小用了,不过管他呢,好用才是硬道理! 省事才是王道!

下面的例子讲解了如何使用FFMPEG+SDL播放一个AAC音频文件。

一、首先下载SDL

SDL官网地址:http://www.libsdl.org/

我们现在都是在Windows系统下使用,因此直接下载编译好的版本就行了。

基本上的库Windows的版本都有提供已经编译好的版本,SDL也是如此:

 

从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用

 

我们用的是mingw的编译器,因此选择下载mingw的版本。

同时我们我们前面配置的编译器是32位的,因此要选择32位的版本。

 

 

二、引用SDL

    当然在这之前,你需要先新建一个Qt工程,怎么新建就不说了,请参考前面的文章,懒得动手的话,请直接下载本文末尾的提供的工程吧。

    不过学习的时候偷懒不是一个好习惯,而开发的时候偷懒就是强烈推荐的,比如尽量选用现成的东西来实现我们的功能,而不是啥都自己动手,比如这里我们选择SDL来播放音频。

 

这里我们要实现的是使用FFMPEG+SDL来播放音频文件,

为此也需要加入FFMPEG,FFMPEG的引用请参考这篇文章:

从零开始学习音视频编程技术(四) FFMPEG的使用

 

 

这个步骤就不上截图了,引用SDL过程中如有疑问请参考前面引用FFMPEG的步骤。

 

SDL下载完成后,首先将LIB库解压出来。

一样的方法,改名成SDL2,并只留下include和lib 这2个文件夹。

将SDL2拷贝到工程目录下,在工程的pro文件中加入:

INCLUDEPATH += $$PWD/ffmpeg/include \
               $$PWD/SDL2/include \ 

LIBS += $$PWD/ffmpeg/lib/avcodec.lib \
        $$PWD/ffmpeg/lib/avdevice.lib \
        $$PWD/ffmpeg/lib/avfilter.lib \
        $$PWD/ffmpeg/lib/avformat.lib \
        $$PWD/ffmpeg/lib/avutil.lib \
        $$PWD/ffmpeg/lib/postproc.lib \
        $$PWD/ffmpeg/lib/swresample.lib \
        $$PWD/ffmpeg/lib/swscale.lib \
        $$PWD/SDL2/lib/x86/SDL2.lib

对应的头文件部分,我们需要加入SDL的了:

extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"

    #include <SDL.h>
    #include <SDL_audio.h>
    #include <SDL_types.h>
    #include <SDL_name.h>
    #include <SDL_main.h>
    #include <SDL_config.h>
}

这里是同时引用了FFMPEG和SDL的工程文件中需要加入的内容。

 

三、编写代码

加入SDL的头文件之后,编译的时候会提示main函数没有定义

原因是 SDL_main.h中有如下一段话:

#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE)
#define main    SDL_main
#endif

可以看出这里已经定义了一个main了,因此我们写的程序中的main便不能生效了,解决方法:

在我们自己写的main函数的前面加上:

#undef main
int main(int argc, char* argv[])
{
    ...
}

若不加上面的#undef main,我们在编译的时候就会编译不通过。

FFMPEG读取音频文件和解码音频的代码,不做介绍了,基本上和前面视频的操作类似。

这里需要注意的一点是:

    SDL播放音频是通过回调函数的方式播放(就是QT中所说的信号与曹了),且这个回调函数是在新的线程中运行,此回调函数固定时间激发一次,这个时间和要播放的音频频率有关系。

    因此我们用FFMPEG读到一帧音频后,不是急着解码,而是将数据存入一个队列,等SDL回调函数激发的时候,从这个队列中取出数据,然后解码 播放。

    代码这里不做介绍了,完整的工程:https://download.csdn.net/download/lailaiquququ11/10704409