• SDL音视频渲染


    01-SDL简介

    官网:https://www.libsdl.org/
    文档:http://wiki.libsdl.org/Introduction

    SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。

    02-Windows环境搭建

    下载地址:https://www.libsdl.org/download-2.0.php

    先直接下载dll和lib使用

    MinGW:Minimalist GNU for Windows

    案例

    将SDL2-2.0.10拷贝到工程目录
    将 SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. .prc
    2. win32 {
    3. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    4. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    5. }
    1. main.c
    2. #include <stdio.h>
    3. #include <SDL.h>
    4. #undef main
    5. int main()
    6. {
    7. printf("Hello World!\n");
    8. SDL_Window *window = NULL; // 声明窗口
    9. SDL_Init(SDL_INIT_VIDEO); // 初始化SDL
    10. // 创建SDL Window
    11. window = SDL_CreateWindow("Basic Window", // 标题
    12. SDL_WINDOWPOS_UNDEFINED, // x
    13. SDL_WINDOWPOS_UNDEFINED, // y
    14. 640, //
    15. 480, //
    16. SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);// 类型
    17. if(!window) // 检测是否创建成功
    18. {
    19. printf("创建 window 失败, err:%s\n", SDL_GetError());
    20. return 1;
    21. }
    22. SDL_Delay(10000); // 延迟10000ms
    23. SDL_DestroyWindow(window); // 销毁窗口
    24. SDL_Quit(); // 释放资源
    25. return 0;
    26. }

    03-Linux环境搭建

    下载地址:https://www.libsdl.org/download-2.0.php

    1. 下载SDL源码库,SDL2-2.0.10.tar.gz
    2. 解压,然后依次执行命令
      ./configure
      make
      sudo make install.
    3. 如果出现Could not initialize SDL - No available video device(Did you set the DISPLAY variable?)错误
      说明系统中没有安装x11的库文件,因此编译出来的SDL库实际上不能用。
      下载安装
      sudo apt-get install libx11-dev
      sudo apt-get install xorg-dev

    04-SDL子系统

    SDL将功能分成下列数个子系统(subsystem):
    ◼ SDL_INIT_TIMER:定时器
    ◼ SDL_INIT_AUDIO:音频
    ◼ SDL_INIT_VIDEO:视频
    ◼ SDL_INIT_JOYSTICK:摇杆
    ◼ SDL_INIT_HAPTIC:触摸屏
    ◼ SDL_INIT_GAMECONTROLLER:游戏控制器
    ◼ SDL_INIT_EVENTS:事件
    ◼ SDL_INIT_EVERYTHING:包含上述所有选项

    05-SDL Window显示:SDL视频显示函数简介

    ◼ SDL_Init():初始化SDL系统
    ◼ SDL_CreateWindow():创建窗口SDL_Window
    ◼ SDL_CreateRenderer():创建渲染器SDL_Renderer
    ◼ SDL_CreateTexture():创建纹理SDL_Texture
    ◼ SDL_UpdateTexture():设置纹理的数据
    ◼ SDL_RenderCopy():将纹理的数据拷贝给渲染器
    ◼ SDL_RenderPresent():显示
    ◼ SDL_Delay():工具函数,用于延时
    ◼ SDL_Quit():退出SDL系统

    06-SDL Windows显示:SDL数据结构简介

    ◼ SDL_Window 代表了一个“窗口”
    ◼ SDL_Renderer 代表了一个“渲染器”
    ◼ SDL_Texture 代表了一个“纹理”
    ◼ SDL_Rect 一个简单的矩形结构

    存储RGB和存储纹理的区别:
    比如一个从左到右由红色渐变到蓝色的矩形,用存储RGB的话就需要把矩形中每个点的具体颜色值存储下来;而纹理只是一些描述信息,比如记录了矩形的大小、起始颜色、终止颜色等信息,显卡可以通过这些信息推算出矩形块的详细信息。
    所以相对于存储RGB而已,存储纹理占用的内存要少的多。

    案例

    新建工程 02-sdl-window
    将SDL2-2.0.10拷贝到工程目录
    将 SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. .prc
    2. win32 {
    3. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    4. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    5. }
    1. main.c
    2. #include <stdio.h>
    3. #include <SDL.h>
    4. #undef main
    5. int main()
    6. {
    7. int run = 1;
    8. SDL_Window *window = NULL;
    9. SDL_Renderer *renderer = NULL;
    10. SDL_Texture *texture = NULL;
    11. SDL_Rect rect; // 长方形,原点在左上角
    12. rect.w = 50; //方块大小
    13. rect.h = 50;
    14. SDL_Init(SDL_INIT_VIDEO);//初始化函数,可以确定希望激活的子系统
    15. window = SDL_CreateWindow("2 Window",
    16. SDL_WINDOWPOS_UNDEFINED,
    17. SDL_WINDOWPOS_UNDEFINED,
    18. 640,
    19. 480,
    20. SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);// 创建窗口
    21. if (!window)
    22. {
    23. return -1;
    24. }
    25. renderer = SDL_CreateRenderer(window, -1, 0);//基于窗口创建渲染器
    26. if (!renderer)
    27. {
    28. return -1;
    29. }
    30. texture = SDL_CreateTexture(renderer,
    31. SDL_PIXELFORMAT_RGBA8888,
    32. SDL_TEXTUREACCESS_TARGET,
    33. 640,
    34. 480); //创建纹理
    35. if (!texture)
    36. {
    37. return -1;
    38. }
    39. int show_count = 0;
    40. while (run)
    41. {
    42. rect.x = rand() % 600;
    43. rect.y = rand() % 400;
    44. SDL_SetRenderTarget(renderer, texture); // 设置渲染目标为纹理
    45. SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // 纹理背景为黑色
    46. SDL_RenderClear(renderer); //清屏
    47. SDL_RenderDrawRect(renderer, &rect); //绘制一个长方形
    48. SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); //长方形为白色
    49. SDL_RenderFillRect(renderer, &rect);
    50. SDL_SetRenderTarget(renderer, NULL); //恢复默认,渲染目标为窗口
    51. SDL_RenderCopy(renderer, texture, NULL, NULL); //拷贝纹理到CPU
    52. SDL_RenderPresent(renderer); //输出到目标窗口上
    53. SDL_Delay(300);
    54. if(show_count++ > 30)
    55. {
    56. run = 0; // 不跑了
    57. }
    58. }
    59. SDL_DestroyTexture(texture);
    60. SDL_DestroyRenderer(renderer);
    61. SDL_DestroyWindow(window); //销毁窗口
    62. SDL_Quit();
    63. return 0;
    64. }

    运行

    07-SDL事件

    SDL事件

    ◼ 函数
    • SDL_WaitEvent():等待一个事件
    • SDL_PushEvent():发送一个事件
    • SDL_PumpEvents():将硬件设备产生的事件放入事件队列,用于读取事件,在调用该函数之前,必须调用SDL_PumpEvents搜集键盘等事件
    • SDL_PeepEvents():从事件队列提取一个事件

    ◼ 数据结构
    • SDL_Event:代表一个事件

    案例

    新建工程 03-sdl-event
    将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. 03-sdl-event.pro
    2. TEMPLATE = app
    3. CONFIG += console
    4. CONFIG -= app_bundle
    5. CONFIG -= qt
    6. SOURCES += main.c
    7. win32 {
    8. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    9. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    10. }
    1. main.c
    2. #include <stdio.h>
    3. #include <SDL.h>
    4. #define FF_QUIT_EVENT (SDL_USEREVENT + 2) // 用户自定义事件
    5. #undef main
    6. int main(int argc, char* argv[])
    7. {
    8. SDL_Window *window = NULL; // Declare a pointer
    9. SDL_Renderer *renderer = NULL;
    10. SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2
    11. // Create an application window with the following settings:
    12. window = SDL_CreateWindow(
    13. "An SDL2 window", // window title
    14. SDL_WINDOWPOS_UNDEFINED, // initial x position
    15. SDL_WINDOWPOS_UNDEFINED, // initial y position
    16. 640, // width, in pixels
    17. 480, // height, in pixels
    18. SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS// flags - see below
    19. );
    20. // Check that the window was successfully created
    21. if (window == NULL)
    22. {
    23. // In the case that the window could not be made...
    24. printf("Could not create window: %s\n", SDL_GetError());
    25. return 1;
    26. }
    27. /* We must call SDL_CreateRenderer in order for draw calls to affect this window. */
    28. renderer = SDL_CreateRenderer(window, -1, 0);
    29. /* Select the color for drawing. It is set to red here. */
    30. SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    31. /* Clear the entire screen to our selected color. */
    32. SDL_RenderClear(renderer);
    33. /* Up until now everything was drawn behind the scenes.
    34. This will show the new, red contents of the window. */
    35. SDL_RenderPresent(renderer);
    36. SDL_Event event;
    37. int b_exit = 0;
    38. for (;;)
    39. {
    40. SDL_WaitEvent(&event);
    41. switch (event.type)
    42. {
    43. case SDL_KEYDOWN: /* 键盘事件 */
    44. switch (event.key.keysym.sym)
    45. {
    46. case SDLK_a:
    47. printf("key down a\n");
    48. break;
    49. case SDLK_s:
    50. printf("key down s\n");
    51. break;
    52. case SDLK_d:
    53. printf("key down d\n");
    54. break;
    55. case SDLK_q:
    56. printf("key down q and push quit event\n");
    57. SDL_Event event_q;
    58. event_q.type = FF_QUIT_EVENT;
    59. SDL_PushEvent(&event_q);
    60. break;
    61. default:
    62. printf("key down 0x%x\n", event.key.keysym.sym);
    63. break;
    64. }
    65. break;
    66. case SDL_MOUSEBUTTONDOWN: /* 鼠标按下事件 */
    67. if (event.button.button == SDL_BUTTON_LEFT)
    68. {
    69. printf("mouse down left\n");
    70. }
    71. else if(event.button.button == SDL_BUTTON_RIGHT)
    72. {
    73. printf("mouse down right\n");
    74. }
    75. else
    76. {
    77. printf("mouse down %d\n", event.button.button);
    78. }
    79. break;
    80. case SDL_MOUSEMOTION: /* 鼠标移动事件 */
    81. printf("mouse movie (%d,%d)\n", event.button.x, event.button.y);
    82. break;
    83. case FF_QUIT_EVENT:
    84. printf("receive quit event\n");
    85. b_exit = 1;
    86. break;
    87. }
    88. if(b_exit)
    89. break;
    90. }
    91. //destory renderer
    92. if (renderer)
    93. SDL_DestroyRenderer(renderer);
    94. // Close and destroy the window
    95. if (window)
    96. SDL_DestroyWindow(window);
    97. // Clean up
    98. SDL_Quit();
    99. return 0;
    100. }

    构建项目
    将 SDL2.dll 拷贝到 build-03-sdl-event-Desktop_Qt_5_10_1_MSVC2015_32bit-Debug 目录
    运行

    08-SDL线程

    SDL多线程
    ◼ SDL线程创建:SDL_CreateThread
    ◼ SDL线程等待:SDL_WaitThead
    ◼ SDL互斥锁:SDL_CreateMutex/SDL_DestroyMutex
    ◼ SDL锁定互斥:SDL_LockMutex/SDL_UnlockMutex
    ◼ SDL条件变量(信号量):SDL_CreateCond/SDL_DestoryCond
    ◼ SDL条件变量(信号量)等待/通知:SDL_CondWait/SDL_CondSingal

    案例

    新建工程 04-sdl-thread
    将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. 04-sdl-thread.pro
    2. TEMPLATE = app
    3. CONFIG += console
    4. CONFIG -= app_bundle
    5. CONFIG -= qt
    6. SOURCES += main.c
    7. win32 {
    8. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    9. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    10. }
    1. main.c
    2. #include <SDL.h>
    3. #include <stdio.h>
    4. SDL_mutex *s_lock = NULL;
    5. SDL_cond *s_cond = NULL;
    6. int thread_work(void *arg)
    7. {
    8. SDL_LockMutex(s_lock);
    9. printf(" <============thread_work sleep\n");
    10. sleep(10); // 用来测试获取锁
    11. printf(" <============thread_work wait\n");
    12. // 释放s_lock资源,并等待signal。之所以释放s_lock是让别的线程能够获取到s_lock
    13. SDL_CondWait(s_cond, s_lock); //另一个线程(1)发送signal和(2)释放lock后,这个函数退出
    14. printf(" <===========thread_work receive signal, continue to do ~_~!!!\n");
    15. printf(" <===========thread_work end\n");
    16. SDL_UnlockMutex(s_lock);
    17. return 0;
    18. }
    19. #undef main
    20. int main()
    21. {
    22. s_lock = SDL_CreateMutex();
    23. s_cond = SDL_CreateCond();
    24. SDL_Thread * t = SDL_CreateThread(thread_work,"thread_work",NULL);
    25. if(!t)
    26. {
    27. printf(" %s",SDL_GetError);
    28. return -1;
    29. }
    30. for(int i = 0;i< 2;i++)
    31. {
    32. sleep(2);
    33. printf("main execute =====>\n");
    34. }
    35. printf("main SDL_LockMutex(s_lock) before ====================>\n");
    36. SDL_LockMutex(s_lock); // 获取锁,但是子线程还拿着锁
    37. printf("main ready send signal====================>\n");
    38. printf("main SDL_CondSignal(s_cond) before ====================>\n");
    39. SDL_CondSignal(s_cond); // 发送信号,唤醒等待的线程
    40. printf("main SDL_CondSignal(s_cond) after ====================>\n");
    41. sleep(10);
    42. SDL_UnlockMutex(s_lock);// 释放锁,让其他线程可以拿到锁
    43. printf("main SDL_UnlockMutex(s_lock) after ====================>\n");
    44. SDL_WaitThread(t, NULL);
    45. SDL_DestroyMutex(s_lock);
    46. SDL_DestroyCond(s_cond);
    47. return 0;
    48. }

    构建项目
    将 SDL2.dll 拷贝到 build-04-sdl-thread-Desktop_Qt_5_10_1_MSVC2015_32bit-Debug 目录
    运行

    09-SDL YUV显示:SDL视频显示的流程

    案例

    新建工程 05-sdl-yuv
    将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. 05-sdl-yuv.pro
    2. TEMPLATE = app
    3. CONFIG += console
    4. CONFIG -= app_bundle
    5. CONFIG -= qt
    6. SOURCES += main.c
    7. win32 {
    8. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    9. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    10. }
    1. main.c
    2. #include <stdio.h>
    3. #include <string.h>
    4. #include <SDL.h>
    5. //自定义消息类型
    6. #define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
    7. #define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件
    8. //定义分辨率
    9. // YUV像素分辨率
    10. #define YUV_WIDTH 320
    11. #define YUV_HEIGHT 240
    12. //定义YUV格式
    13. #define YUV_FORMAT SDL_PIXELFORMAT_IYUV
    14. int s_thread_exit = 0; // 退出标志 = 1则退出
    15. int refresh_video_timer(void *data)
    16. {
    17. while (!s_thread_exit)
    18. {
    19. SDL_Event event;
    20. event.type = REFRESH_EVENT;
    21. SDL_PushEvent(&event);
    22. SDL_Delay(40);
    23. }
    24. s_thread_exit = 0;
    25. //push quit event
    26. SDL_Event event;
    27. event.type = QUIT_EVENT;
    28. SDL_PushEvent(&event);
    29. return 0;
    30. }
    31. #undef main
    32. int main(int argc, char* argv[])
    33. {
    34. //初始化 SDL
    35. if(SDL_Init(SDL_INIT_VIDEO))
    36. {
    37. fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    38. return -1;
    39. }
    40. // SDL
    41. SDL_Event event; // 事件
    42. SDL_Rect rect; // 矩形
    43. SDL_Window *window = NULL; // 窗口
    44. SDL_Renderer *renderer = NULL; // 渲染
    45. SDL_Texture *texture = NULL; // 纹理
    46. SDL_Thread *timer_thread = NULL; // 请求刷新线程
    47. uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV
    48. // 分辨率
    49. // 1. YUV的分辨率
    50. int video_width = YUV_WIDTH;
    51. int video_height = YUV_HEIGHT;
    52. // 2.显示窗口的分辨率
    53. int win_width = YUV_WIDTH;
    54. int win_height = YUV_WIDTH;
    55. // YUV文件句柄
    56. FILE *video_fd = NULL;
    57. const char *yuv_path = "yuv420p_320x240.yuv";
    58. size_t video_buff_len = 0;
    59. uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面
    60. // 我们测试的文件是YUV420P格式
    61. uint32_t y_frame_len = video_width * video_height;
    62. uint32_t u_frame_len = video_width * video_height / 4;
    63. uint32_t v_frame_len = video_width * video_height / 4;
    64. uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;
    65. //创建窗口
    66. window = SDL_CreateWindow("Simplest YUV Player",
    67. SDL_WINDOWPOS_UNDEFINED,
    68. SDL_WINDOWPOS_UNDEFINED,
    69. video_width, video_height,
    70. SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
    71. if(!window)
    72. {
    73. fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError());
    74. goto _FAIL;
    75. }
    76. // 基于窗口创建渲染器
    77. renderer = SDL_CreateRenderer(window, -1, 0);
    78. // 基于渲染器创建纹理
    79. texture = SDL_CreateTexture(renderer,
    80. pixformat,
    81. SDL_TEXTUREACCESS_STREAMING,
    82. video_width,
    83. video_height);
    84. // 分配空间
    85. video_buf = (uint8_t*)malloc(yuv_frame_len);
    86. if(!video_buf)
    87. {
    88. fprintf(stderr, "Failed to alloce yuv frame space!\n");
    89. goto _FAIL;
    90. }
    91. // 打开YUV文件
    92. video_fd = fopen(yuv_path, "rb");
    93. if( !video_fd )
    94. {
    95. fprintf(stderr, "Failed to open yuv file\n");
    96. goto _FAIL;
    97. }
    98. // 创建请求刷新线程
    99. timer_thread = SDL_CreateThread(refresh_video_timer,
    100. NULL,
    101. NULL);
    102. while (1)
    103. {
    104. // 收取SDL系统里面的事件
    105. SDL_WaitEvent(&event);
    106. if(event.type == REFRESH_EVENT) // 画面刷新事件
    107. {
    108. video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);
    109. if(video_buff_len <= 0)
    110. {
    111. fprintf(stderr, "Failed to read data from yuv file!\n");
    112. goto _FAIL;
    113. }
    114. // 设置纹理的数据 video_width = 320, plane
    115. SDL_UpdateTexture(texture, NULL, video_buf, video_width);
    116. // 显示区域,可以通过修改w和h进行缩放
    117. rect.x = 0;
    118. rect.y = 0;
    119. float w_ratio = win_width * 1.0 /video_width;
    120. float h_ratio = win_height * 1.0 /video_height;
    121. // 320x240 怎么保持原视频的宽高比例
    122. rect.w = video_width * w_ratio;
    123. rect.h = video_height * h_ratio;
    124. // rect.w = video_width * 0.5;
    125. // rect.h = video_height * 0.5;
    126. // 清除当前显示
    127. SDL_RenderClear(renderer);
    128. // 将纹理的数据拷贝给渲染器
    129. SDL_RenderCopy(renderer, texture, NULL, &rect);
    130. // 显示
    131. SDL_RenderPresent(renderer);
    132. }
    133. else if(event.type == SDL_WINDOWEVENT)
    134. {
    135. //If Resize
    136. SDL_GetWindowSize(window, &win_width, &win_height);
    137. printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width,
    138. win_height );
    139. }
    140. else if(event.type == SDL_QUIT) //退出事件
    141. {
    142. s_thread_exit = 1;
    143. }
    144. else if(event.type == QUIT_EVENT)
    145. {
    146. break;
    147. }
    148. }
    149. _FAIL:
    150. s_thread_exit = 1; // 保证线程能够退出
    151. // 释放资源
    152. if(timer_thread)
    153. SDL_WaitThread(timer_thread, NULL); // 等待线程退出
    154. if(video_buf)
    155. free(video_buf);
    156. if(video_fd)
    157. fclose(video_fd);
    158. if(texture)
    159. SDL_DestroyTexture(texture);
    160. if(renderer)
    161. SDL_DestroyRenderer(renderer);
    162. if(window)
    163. SDL_DestroyWindow(window);
    164. SDL_Quit();
    165. return 0;
    166. }

    构建项目
    将 SDL2.dll 拷贝到 build-05-sdl-yuv-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录
    将 yuv420p_320x240.yuv 文件 拷贝到build-05-sdl-yuv-Desktop_Qt_5_10_1_MinGW_32bit-Debug目录

    运行

    10-SDL播放音频PCM-打开音频设备

    打开音频设备

    1. int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);
    2. // desired:期望的参数。
    3. // obtained:实际音频设备的参数,一般情况下设置为NULL即可。

    SDL_AudioSpec

    1. typedef struct SDL_AudioSpec {
    2. int freq; // 音频采样率
    3. SDL_AudioFormat format; // 音频数据格式
    4. Uint8 channels; // 声道数: 1 单声道, 2 立体声
    5. Uint8 silence; // 设置静音的值,因为声音采样是有符号的,所以0当然就是这个值
    6. Uint16 samples; // 音频缓冲区中的采样个数,要求必须是2的n次
    7. Uint16 padding; // 考虑到兼容性的一个参数
    8. Uint32 size; // 音频缓冲区的大小,以字节为单位
    9. SDL_AudioCallback callback; // 填充音频缓冲区的回调函数
    10. void *userdata; // 用户自定义的数据
    11. } SDL_AudioSpec;

    11-SDL播放音频PCM-SDL_AudioCallback

    SDL_AudioCallback

    1. // userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
    2. // stream:该指针指向需要填充的音频缓冲区。
    3. // len:音频缓冲区的大小(以字节为单位)1024*2*2
    4. void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 *stream, int len);

    播放音频数据

    1. // 当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会
    2. 播放静音的值。
    3. void SDLCALL SDL_PauseAudio(int pause_on)

    12-SDL播放音频PCM-代码

    案例

    新建工程 06-sdl-pcm
    将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

    1. 06-sdl-pcm.pro
    2. TEMPLATE = app
    3. CONFIG += console
    4. CONFIG -= app_bundle
    5. CONFIG -= qt
    6. SOURCES += main.c
    7. win32 {
    8. INCLUDEPATH += $$PWD/SDL2-2.0.10/include
    9. LIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
    10. }
    1. main.c
    2. /**
    3. * SDL2播放PCM
    4. *
    5. * 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图
    6. * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层
    7. * API。
    8. * 测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2
    9. *
    10. * 函数调用步骤如下:
    11. *
    12. * [初始化]
    13. * SDL_Init(): 初始化SDL。
    14. * SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。
    15. * SDL_PauseAudio(): 播放音频数据。
    16. *
    17. * [循环播放数据]
    18. * SDL_Delay(): 延时等待播放完成。
    19. *
    20. */
    21. #include <stdio.h>
    22. #include <SDL.h>
    23. // 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
    24. #define PCM_BUFFER_SIZE (1024*2*2*2)
    25. // 音频PCM数据缓存
    26. static Uint8 *s_audio_buf = NULL;
    27. // 目前读取的位置
    28. static Uint8 *s_audio_pos = NULL;
    29. // 缓存结束位置
    30. static Uint8 *s_audio_end = NULL;
    31. //音频设备回调函数
    32. void fill_audio_pcm(void *udata, Uint8 *stream, int len)
    33. {
    34. SDL_memset(stream, 0, len);
    35. if(s_audio_pos >= s_audio_end) // 数据读取完毕
    36. {
    37. return;
    38. }
    39. // 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少)
    40. int remain_buffer_len = s_audio_end - s_audio_pos;
    41. len = (len < remain_buffer_len) ? len : remain_buffer_len;
    42. // 拷贝数据到stream并调整音量
    43. SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME/8);
    44. printf("len = %d\n", len);
    45. s_audio_pos += len; // 移动缓存指针
    46. }
    47. // 提取PCM文件
    48. // ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
    49. // 测试PCM文件
    50. // ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
    51. #undef main
    52. int main(int argc, char *argv[])
    53. {
    54. int ret = -1;
    55. FILE *audio_fd = NULL;
    56. SDL_AudioSpec spec;
    57. const char *path = "44100_16bit_2ch.pcm";
    58. // 每次缓存的长度
    59. size_t read_buffer_len = 0;
    60. //SDL initialize
    61. if(SDL_Init(SDL_INIT_AUDIO)) // 支持AUDIO
    62. {
    63. fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    64. return ret;
    65. }
    66. //打开PCM文件
    67. audio_fd = fopen(path, "rb");
    68. if(!audio_fd)
    69. {
    70. fprintf(stderr, "Failed to open pcm file!\n");
    71. goto _FAIL;
    72. }
    73. s_audio_buf = (uint8_t *)malloc(PCM_BUFFER_SIZE);
    74. // 音频参数设置SDL_AudioSpec
    75. spec.freq = 44100; // 采样频率
    76. spec.format = AUDIO_S16SYS; // 采样点格式
    77. spec.channels = 2; // 2通道
    78. spec.silence = 0;
    79. spec.samples = 1024; // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samples
    80. spec.callback = fill_audio_pcm; // 回调函数
    81. spec.userdata = NULL;
    82. //打开音频设备
    83. if(SDL_OpenAudio(&spec, NULL))
    84. {
    85. fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
    86. goto _FAIL;
    87. }
    88. //play audio
    89. SDL_PauseAudio(0);
    90. int data_count = 0;
    91. while(1)
    92. {
    93. // 从文件读取PCM数据
    94. read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
    95. if(read_buffer_len == 0)
    96. {
    97. break;
    98. }
    99. data_count += read_buffer_len; // 统计读取的数据总字节数
    100. printf("now playing %10d bytes data.\n",data_count);
    101. s_audio_end = s_audio_buf + read_buffer_len; // 更新buffer的结束位置
    102. s_audio_pos = s_audio_buf; // 更新buffer的起始位置
    103. //the main thread wait for a moment
    104. while(s_audio_pos < s_audio_end)
    105. {
    106. SDL_Delay(10); // 等待PCM数据消耗
    107. }
    108. }
    109. printf("play PCM finish\n");
    110. // 关闭音频设备
    111. SDL_CloseAudio();
    112. _FAIL:
    113. //release some resources
    114. if(s_audio_buf)
    115. free(s_audio_buf);
    116. if(audio_fd)
    117. fclose(audio_fd);
    118. //quit SDL
    119. SDL_Quit();
    120. return 0;
    121. }

    构建项目
    将 SDL2.dll 拷贝到 build-06-sdl-pcm-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录
    将 44100_16bit_2ch.pcm 文件 拷贝到 build-06-sdl-pcm-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录

    运行

  • 相关阅读:
    HarmonyOS/OpenHarmony原生应用开发-华为Serverless云端服务支持说明(一)
    node---express
    sealos踩坑记录
    六、Git远程仓库操作——创建远程库、推送拉取和克隆远程库等操作
    测试开发工程师到底是做什么的?
    LLM大模型工程师面试经验宝典--基础版(2024.7月最新)
    华为OD机试 - 数字序列比大小 - 贪心算法(Java 2023 B卷 100分)
    Amazon EKS绑定alb 使用aws-load-balancer-controller(Ingress Controller)对外提供服务
    135. 分发糖果
    mysql修改字段的更新时间
  • 原文地址:https://blog.csdn.net/qq_33301482/article/details/134447318