一:SDL介绍与安装

(一)SDL介绍

FFmpeg编程(三)SDL开发_初始化

(二)SDL安装

FFmpeg编程(三)SDL开发_初始化_02 

1.源码下载:http://www.libsdl.org/download-2.0.php

2.生成Makefile文件

./configure --prefix=/usr/local

3.安装

sudo make -j 8 && sudo make install

FFmpeg编程(三)SDL开发_数据_03

FFmpeg编程(三)SDL开发_FFmpeg_04

FFmpeg编程(三)SDL开发_初始化_05

FFmpeg编程(三)SDL开发_#include_06

二:SDL的简单使用

SDL播放视频的代码流程如下所示:

初始化: 

SDL_Init(): 初始化SDL。 

SDL_CreateWindow(): 创建窗口(Window)。 

SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。 

SDL_CreateTexture(): 创建纹理(Texture)。

循环渲染数据: 

SDL_UpdateTexture(): 设置纹理的数据。 

SDL_RenderCopy(): 纹理复制给渲染器。
SDL_RenderPresent(): 显示。

(一)基本使用步骤

FFmpeg编程(三)SDL开发_初始化_07

(二)SDL渲染窗口

FFmpeg编程(三)SDL开发_#include_08

1.SDL_Init/SDL_Quit 初始化和退出操作

javascript:void(0)

int SDLCALL SDL_Init(Uint32 flags)

其中,flags可以取下列值:

SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个不理解)
SDL_INIT_EVERYTHING:包含上述所有选项

2.SDL_CreateWindow()/SDL_DestroyWindow() 创建窗口(比如将图片渲染到窗口)

javascript:void(0)

SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);

参数含义如下:javascript:void(0)

title :窗口标题
x :窗口位置x坐标。也可以设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。
y :窗口位置y坐标。同上。
w :窗口的宽
h :窗口的高
flags :支持下列标识。包括了窗口的是否最大化、最小化,能否调整边界等等属性。
       ::SDL_WINDOW_FULLSCREEN,    ::SDL_WINDOW_OPENGL,
       ::SDL_WINDOW_HIDDEN,        ::SDL_WINDOW_BORDERLESS,
       ::SDL_WINDOW_RESIZABLE,     ::SDL_WINDOW_MAXIMIZED,
       ::SDL_WINDOW_MINIMIZED,     ::SDL_WINDOW_INPUT_GRABBED,
       ::SDL_WINDOW_ALLOW_HIGHDPI.
 返回创建完成的窗口的ID。如果创建失败则返回0。

3.SDL_CreateRenderer() 创建渲染器(将图像/视频帧汇聚到窗口)

SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);

参数含义如下:

window : 渲染的目标窗口。
index :打算初始化的渲染设备的索引。设置“-1”则初始化默认的渲染设备。
flags :支持以下值(位于SDL_RendererFlags定义中)
    SDL_RENDERER_SOFTWARE :使用软件渲染
    SDL_RENDERER_ACCELERATED :使用硬件加速
    SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步
    SDL_RENDERER_TARGETTEXTURE :不太懂
返回创建完成的渲染器的ID。如果创建失败则返回NULL。

(三)简单实例(未渲染)

FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    SDL_Window* wind = NULL;

    SDL_Init(SDL_INIT_VIDEO);    //进行视频初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //显示、有边界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    while(1){
        sleep(5000);
    }

    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 01_sdl_sample.c -o 01ss -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

FFmpeg编程(三)SDL开发_FFmpeg_11

可以看到出现窗口,但是没有渲染,导致出现透明状态,显示屏幕原本信息 

(四)简单实例(使用渲染器)

FFmpeg编程(三)SDL开发_#include_12

SDL_Renderer表示渲染上下文。这意味着它包含与渲染相关的所有当前设置,以及有关如何渲染当前帧的说明。

要创建渲染上下文,可以使用函数SDL_CreateWindowAndRenderer()或SDL_CreateRenderer()。前者同时创建窗口和渲染器。后者要求我们先创建一个窗口。

在程序中,您将使用SDL_SetRenderDrawColor()等函数更改上下文中的设置,并使用SDL_RenderDrawPoint()等函数执行渲染操作。

int SDL_RenderClear(SDL_Renderer* renderer) ,该函数的作用是用指定的颜色清空缓冲区。renderer是上面创建的渲染器上下文。

void SDL_RenderPresent(SDL_Renderer* renderer),将缓冲区中的内容输出到目标上,也就是 windows 窗口上。注意:渲染的数据也可以是从GPU渲染到屏幕!!

FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;

    SDL_Init(SDL_INIT_VIDEO);    //进行视频初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //显示、有边界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //创建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //设置颜色和透明度(可选操作)
    SDL_RenderClear(rend);            //清屏处理(清空render之前的数据)
    SDL_RenderPresent(rend);            //将渲染器结果放入窗口中显示出来

    SDL_Delay(5000);
__DWIND:
    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code

FFmpeg编程(三)SDL开发_数据_15

(五)SDL_Surface与SDL_Texture

SDL 渲染的工作原理:

在SDL_Render对象中有一个视频缓冲区,该缓冲区我们称之为SDL_Surface,它是按照像素存放图像的。
我们一般把真彩色的像素称为RGB24数据。
也就是说,每一个像素由24位组成,每8位代表一种颜色,像素的最终颜色是由RGB三种颜色混合而成的。

SDL_Texture 与SDL_Surface相似,也是一种缓冲区

只不过它存放的不是真正的像素数据,而是存放的图像的描述信息。这些描述信息通过OpenGL、D3D 或 Metal等技术操作GPU,从而绘制出与SDL_Surface一样的图形,且效率更高(因为它是GPU硬件计算的)。

(六)SDL_Window与SDL_Render

SDL_Window代表的是窗口的逻辑概念,它是存放在主内存中的一个对象。

SDL_Render 是渲染器,它也是主存中的一个对象。对Render操作时实际上分为两个阶段

一、渲染阶段。在该阶段,用户可以画各种图形渲染到SDL_Surface或SDL_Texture 中;

二、显示阶段。参SDL_Texture为数据,通过OpenGL操作GPU,最终将 SDL_Surfce 或SDL_Texture中的数据输出到显示器上。

三:SDL事件 

(一)SDL事件基本原理

FFmpeg编程(三)SDL开发_#include_16

(二)SDL事件种类

FFmpeg编程(三)SDL开发_渲染器_17

(三)SDL事件处理

FFmpeg编程(三)SDL开发_#include_18

1.poll 论询机制,处理不及时,占CPU
2.wait 事件触发(类似epoll池),处理及时,不占用过多CPU。

对于wait,可能出现线程阻塞,导致无法处理到来的其他事件,应该为每一个时间处理设置Timeout,所以出现了WaitEventTimeOut

(四)事件机制简单使用 

FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    int exitFlag=1;
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Event event;

    SDL_Init(SDL_INIT_VIDEO);    //进行视频初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //显示、有边界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //创建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //设置颜色和透明度(可选操作)
    SDL_RenderClear(rend);            //清屏处理(清空render之前的数据)
    SDL_RenderPresent(rend);            //将渲染器结果放入窗口中显示出来

    while(exitFlag){
        SDL_WaitEvent(&event);    //如果没有事件的话,会阻塞在这里的,不会一直while轮询
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }
    }


__DWIND:
    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 02_sdl_event.c -o 02se -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

FFmpeg编程(三)SDL开发_FFmpeg_21

四:纹理渲染

依据SDL编程方式,这里又分为两种情况:

1. 不使用纹理

由cpu直接绘制一幅画(cpu需要将最原始的rgb/YUV数据,刷到屏幕上), 相当于学生小A直接在墙上画画

2. 使用纹理

相当于小A同学(cpu)指挥画家(gpu)在纸上画, 然后把纸贴在墙上。 
这个过程中画是由画家(gpu)画的, 小A同学负责发号施令(即告诉画家画什么), 纸代表纹理, 画家代表gpu, 所有绘制的操作都是在纹理上进行。
事实上,纹理的概念并不仅仅是一张纸, 还包括小A同学中对这幅画的构思,可以理解成画画的算法, 而纸相当于是一个载体(内存空间,用于保存这些构思)。
gpu根据纹理就可以计算出这幅图每个像素点的颜色( 相当于画家根据小A同学的描述,画出一幅画一样)

可以看出,使用纹理,可以减轻cpu的负担, cpu处于一个发号施令的角色,图片的计算过程交给效率更好的gpu来做,可以提高渲染的效率

(一)纹理渲染基本原理

FFmpeg编程(三)SDL开发_初始化_22

图像结果渲染器,变为纹理(即对这张图像的描述数据),将纹理交给显卡GPU,GPU经过计算,将图像显示在窗口中! 

(二)SDL纹理相关API

FFmpeg编程(三)SDL开发_#include_23

SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, Uint32 format, int access, int w, int h); 

参数的含义如下:

renderer:目标渲染器。
format :纹理的格式。后面会详述。
access :可以取以下值(定义位于SDL_TextureAccess中)
    SDL_TEXTUREACCESS_STATIC :变化极少
    SDL_TEXTUREACCESS_STREAMING :变化频繁
    SDL_TEXTUREACCESS_TARGET :暂时没有理解
w :纹理的宽
h :纹理的高
创建成功则返回纹理的ID,失败返回0。

(三)渲染相关API

FFmpeg编程(三)SDL开发_渲染器_24

SDL_CreateTexture用于创建纹理, 若要使用SDL_SetRenderTarget()设置渲染到纹理时, 必须使用SDL_TEXTUREACCESS_TARGET方式来创建纹理。

函数SDL_SetRenderTarget()用于在渲染到纹理或屏幕之间进行选择。切换方式如下:

        SDL_SetRenderTarget(renderer, texture) // 渲染到纹理

        SDL_SetRenderTarget(renderer, NULL) // 渲染到屏幕(默认渲染到窗口/屏幕

int SDL_RenderCopy(SDL_Renderer*   renderer, SDL_Texture*  texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)将纹理拷贝到显卡GPU中去,通过SDL_RenderPresent进行渲染到屏幕

renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。

(四)纹理使用

FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    int exitFlag=1;
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Texture* text = NULL;
    SDL_Event event;
    SDL_Rect rect;    //创建矩形

    SDL_Init(SDL_INIT_VIDEO);    //进行视频初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //显示、有边界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //创建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //设置颜色和透明度(可选操作)
    SDL_RenderClear(rend);            //清屏处理(清空render之前的数据)
    SDL_RenderPresent(rend);            //将渲染器结果放入窗口中显示出来

    text = SDL_CreateTexture(rend,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,
                640,480);    //创建纹理
    if(!text){
        printf("Failed to create texture\n");
        goto __DREND;
    }

    rect.w = 30;
    rect.h = 30;

    while(exitFlag){
        SDL_PollEvent(&event);    //如果没有事件的话,会一直while轮询
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }

        rect.x = rand() % 610;
        rect.y = rand() % 450;

        SDL_SetRenderTarget(rend,text);    //渲染到纹理,下面的操作都会转为纹理
        SDL_SetRenderDrawColor(rend,0,0,0,0);
        SDL_RenderClear(rend);

        //绘制矩形
        SDL_RenderDrawRect(rend,&rect);
        SDL_SetRenderDrawColor(rend,255,0,0,0);    //为矩形绘制颜色
        SDL_RenderFillRect(rend,&rect);    //将渲染器设置到矩形中去,使得clear只在矩形中生效

        SDL_SetRenderTarget(rend,NULL);    //使用默认渲染,到窗口
        
        SDL_RenderCopy(rend,text,NULL,NULL);    //拷贝纹理到显卡
        SDL_RenderPresent(rend);                //将结果显示到窗口
    }

    SDL_DestroyTexture(text);
__DREND:
    SDL_DestroyRenderer(rend);
__DWIND:
    SDL_DestroyWindow(wind);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 03_sdl_texture.c -o 03st -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

FFmpeg编程(三)SDL开发_#include_27

        SDL_SetRenderTarget(rend,text);       //渲染到纹理,下面的操作都会转为纹理!!!

SDL_SetRenderDrawColor(rend,0,0,0,0);   SDL_RenderClear(rend);            //绘制矩形 SDL_RenderDrawRect(rend,&rect); SDL_SetRenderDrawColor(rend,255,0,0,0); //为矩形绘制颜色 SDL_RenderFillRect(rend,&rect);    //将渲染器设置到矩形中去,使得内部clear只在矩形中生效 SDL_SetRenderTarget(rend,NULL);    //使用默认渲染,到窗口;下面的操作会显示在窗口,不经过纹理转换 SDL_RenderCopy(rend,text,NULL,NULL); //拷贝纹理到显卡---在窗口层面处理,而不是纹理 SDL_RenderPresent(rend); //GPU显卡将计算的结果显示到窗口
五:实战YUV播放器

(一)创建线程(提高效率)

FFmpeg编程(三)SDL开发_数据_28

创建线程:SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char*  name, void*  data)

fn: 线程要运行的函数。
name: 线程名。
data: 函数参数。

等待线程:void SDL_WaitThread(SDL_Thread* thread, int*   status) 等待线程结束

创建互斥量:SDL_mutex* SDL_CreateMutex(void) 也就是创建一个稀有资源,这样大家就去抢这个资源。从而达到为真正资源加锁的目的。

销毁互斥量:void SDL_DestroyMutex(SDL_mutex* mutex)

加锁: int SDL_LockMutex(SDL_mutex* mutex)

解锁: int SDL_UnlockMutex(SDL_mutex* mutex)

(二)纹理更新

FFmpeg编程(三)SDL开发_#include_29

int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect, const void *pixels, int pitch); 

参数的含义如下:

texture:目标纹理。
rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。
pixels:像素数据。
pitch:一行像素数据的字节数。

成功的话返回0,失败的话返回-1

SDL_UpdateTexture()的大致流程如下:

1. 检查输入参数的合理性。例如像素格式是否支持,宽和高是否小于等于0等等。
2. 如果是一些特殊的格式,进行一定的处理:
a) 如果输入的像素数据是YUV格式的,则会调用SDL_UpdateTextureYUV()进行处理。
b) 如果输入的像素数据的像素格式不是渲染器支持的格式,则会调用SDL_UpdateTextureNative()进行处理3. 调用SDL_Render的UpdateTexture()方法更新纹理。这一步是整个函数的核心。

通过SDL_CreateTexture中指定纹理格式format为SDL_PIXELFORMAT_IYUV即可处理YUV数据!!

(三)编程实现

ffmpeg -i gfxm.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <string.h>
#include <SDL.h>

//下面定义自定义事件,SDL_USEREVENT 0x8000之后,到0xFFFF都可以
#define REFRASH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)

int thread_exit = 0;    //控制线程退出

void refresh_video_timer(void* udata){
    thread_exit = 0;

    while(!thread_exit){
        SDL_Event event;
        event.type = REFRASH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);    //25帧/S
    }

    thread_exit = 0;
    //开始退出
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);
    return 0;
}

int main(int argc,char* argv[]){
    int exitFlag=1;    //控制循环

    char* filename = NULL;
    FILE* fp = NULL;
    int len;

    uint8_t* src_data = NULL,*src_start=NULL;    //获取视频帧数据
    int v_w,v_h,w_w=1280,w_h=720;    //视频宽高、窗口宽高
    unsigned int frame_len,frame_temp_len;

    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Texture* text = NULL;
    SDL_Event event;
    SDL_Rect rect;    //创建矩形
    SDL_Thread* timer_thread = NULL;    //线程

    if(argc<4){
        printf("The number of parameter must be than 3\n");
        return 0;
    }

    filename = argv[1];    //获取YUV文件----YUV为YUV420数据
    v_w = atoi(argv[2]);    //获取文件的宽
    v_h = atoi(argv[3]);    //获取视频的高

    frame_temp_len = v_h*v_w*1.5;    //YUV420数据大小,需要对数据进行对齐操作
    frame_len = frame_temp_len;
    if(frame_temp_len&0xF){    //后4位存在数据,则表示不是16对齐
        frame_len = (frame_temp_len&0xFFF0) + 0x10;    //进16
    }

    if(SDL_Init(SDL_INIT_VIDEO)){    //进行视频初始化
        printf("Can`t initialize SDL\n");
        return -1;
    }

    wind = SDL_CreateWindow("YUV Player",
        SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,    //不指定左上位置
        w_w,w_h,            //设置窗口宽高,与视频一般或者大些都可以
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);    //窗口可用于OpenGL上下文 窗口可以调整大小

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //创建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }

    //创建纹理
    text = SDL_CreateTexture(rend,
                            SDL_PIXELFORMAT_IYUV,    //< Planar mode: Y + U + V  (3 planes)
                            SDL_TEXTUREACCESS_TARGET,
                            v_w,v_h);    //创建纹理
    if(!text){
        printf("Failed to create texture\n");
        goto __DREND;
    }

    //开始读取YUV数据
    fp = fopen(filename,"rb");
    if(!fp){
        printf("Failed to open file:%s\n",filename);
        goto __TEXT;
    }

    src_data = (uint8_t*)malloc(frame_len);    //分配空间
    if(!src_data){
        printf("Failed to alloc memory for src_data!\n");
        goto __FILE;
    }
    src_start = src_data;    //开始位置

    //开启线程,处理其他控制事件
    timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL);

    //要显示纹理数据的矩形
    rect.x = 0;
    rect.y = 0;

    while(exitFlag){
        SDL_PollEvent(&event);    //如果没有事件的话,会一直while轮询
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            case REFRASH_EVENT:    //更新纹理
                //先读取一帧数据
                if((len=fread(src_data,1,frame_len,fp))<=0){    //数据读取完成,退出
                    thread_exit = 1;
                    continue;
                }

                SDL_UpdateTexture(text,NULL,src_start,v_w);    //内部设置
                rect.w = w_w;    //因为窗口可以被重置
                rect.h = w_h;    

                SDL_RenderClear(rend);

                SDL_RenderCopy(rend,text,NULL,&rect);    //拷贝纹理到显卡,选择渲染目标的一块矩形区域作为输出
                SDL_RenderPresent(rend);                //将结果显示到窗口
                break;
            case QUIT_EVENT:    //退出播放
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }
    }

    if(src_data)
        free(src_data);
__FILE:
    if(fp)
        fclose(fp);
__TEXT:
    SDL_DestroyTexture(text);
__DREND:
    SDL_DestroyRenderer(rend);
__DWIND:
    SDL_DestroyWindow(wind);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 04_sdl_yuv.c -o 04sy -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2 
./04sy out.yuv 864 486

FFmpeg编程(三)SDL开发_FFmpeg_32

六:实战PCM播放器

使用 SDL 的音频 API 来播放声音。其基本流程是,从 pcm 文件一块一块的读数据。然后通过 read_audio_data 这个回调函数给声卡喂数据。

如果一次没用完,SDL会再次调用回调函数读数据。如果audio_buf中的数据用完了,则再次从文件中读一块数据,直到读到文件尾。

(一)播放音频基本流程

FFmpeg编程(三)SDL开发_FFmpeg_33

(二)播放音频原则

FFmpeg编程(三)SDL开发_数据_34

(三)相关API

FFmpeg编程(三)SDL开发_数据_35

1.打开音频设备    int SDL_OpenAudio(SDL_AudioSpec* desired,SDL_AudioSpec* obtained) 

desired: 设置音频参数。

参数 说明
freq 每秒采频率
SDL_AudioFormat 音频数据存储格式
channels 通道数
silence 静音值
samples 采样个数
size 音频缓冲区大小
SDL_AudioCallback 回调函数
userdata 回调函数参数指针

obtained: 返回参数。

2.关闭音频设备    void SDL_CloseAudio(void)

3.播放与暂停  void SDL_PauseAudio(int pause_on)

pause_on: 0, 暂停播放;1, 播放;

4.喂数据  void SDL_MixAudio(Uint8* dst,const Uint8* src,Uint32 len,int volume)

dst: 目的缓冲区
src: 源缓冲区
len: 音频数据长度
volume: 音量大小,0-128 之间的数。SDL_MIX_MAXVOLUME代表最大音量。

(四)编程实现

ffmpeg -i out.aac -ar 44100 -ac 2 -f s16le out.pcm
FFmpeg编程(三)SDL开发_FFmpeg_09FFmpeg编程(三)SDL开发_渲染器_10
#include <stdio.h>
#include <string.h>
#include <SDL.h>

#define BLOCK_SIZE 4096000 //4096k,设置一个较大的数值空间

//将数据存放在全局
static uint8_t* audio_buf = NULL;
static uint8_t* audio_pos = NULL;
static int buffer_len = 0;

//回调函数
void read_audio_data(void* udata,uint8_t* stream,int len){    //回调数据,目的缓冲区,和缓冲区长度
    if(buffer_len == 0)
        return;

    SDL_memset(stream,0,len);    //初始化缓冲区

    len = (len<buffer_len)?len:buffer_len;    //获取空间长度
    SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);

    audio_pos += len;
    buffer_len -= len;
}

int main(int argc,char* argv[]){
    int exitFlag=1;    //控制循环

    char* filename = NULL;
    FILE* fp = NULL;

    SDL_AudioSpec spec;    //用来设置音频参数

    if(argc<2){
        printf("The number of parameter must be than 1\n");
        return 0;
    }

    filename = argv[1];    //获取pcm文件

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){    //进行视频初始化
        printf("Can`t initialize SDL\n");
        return -1;
    }

    fp = fopen(filename,"rb");
    if(!fp){
        printf("Failed to open file:%s",filename);
        goto __EXIT;
    }

    audio_buf = (uint8_t*)malloc(BLOCK_SIZE);    //分配空间
    if(!audio_buf){
        printf("Failed to alloc memory for audio buffer\n");
        goto __FILE;
    }

    //开始开启音频设备
    spec.freq = 44100;
    spec.format = AUDIO_S16SYS;
    spec.channels = 2;
    spec.silence = 0;
    spec.samples = 1024;    //每帧数据,单通道的采样数量
    spec.callback = read_audio_data;
    spec.userdata = NULL;

    if(SDL_OpenAudio(&spec,NULL)){
        printf("Failed to open audio device:%s\n",SDL_GetError());
        goto __BUFFER;
    }

    //开始播放
    SDL_PauseAudio(0);

    while(1){
        buffer_len = fread(audio_buf,1,BLOCK_SIZE,fp);
        if(buffer_len<=0)
            break;
        printf("read block size:%d \n",buffer_len);

        audio_pos = audio_buf;

        while(audio_pos<(audio_buf+buffer_len)){    //数据没有读取完成
            SDL_Delay(1);    //延迟1毫秒,等等设备继续读取
        }
    }

    SDL_CloseAudio();    //关闭音频设备
__BUFFER:
    if(audio_buf)
        free(audio_buf);
__FILE:
    if(fp)
        fclose(fp);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code

  gcc 05_sdl_pcm.c -o 05sp -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

./05sp out.pcm

FFmpeg编程(三)SDL开发_渲染器_38