
(音视频学习笔记):SDL-YUV显示-播放音频PCM
发布日期:2021-05-07 15:20:23
浏览次数:9
分类:原创文章
本文共 8821 字,大约阅读时间需要 29 分钟。
- 【说明】课程学习地址:
目录
YUV显示: SDL视频显示的流程
- 示例
#include <stdio.h>#include <string.h>#include <SDL.h>//自定义消息类型#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件#define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件//定义分辨率// YUV像素分辨率#define YUV_WIDTH 320#define YUV_HEIGHT 240//定义YUV格式#define YUV_FORMAT SDL_PIXELFORMAT_IYUVint s_thread_exit = 0; // 退出标志 = 1则退出int refresh_video_timer(void *data){ while (!s_thread_exit) { //处理每帧数据 SDL_Event event; event.type = REFRESH_EVENT; SDL_PushEvent(&event); SDL_Delay(40); } s_thread_exit = 0; //push quit event SDL_Event event; event.type = QUIT_EVENT; SDL_PushEvent(&event); return 0;}#undef mainint main(int argc, char* argv[]){ //初始化 SDL if(SDL_Init(SDL_INIT_VIDEO)) { fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } // SDL SDL_Event event; // 事件 SDL_Rect rect; // 矩形 SDL_Window *window = NULL; // 窗口 SDL_Renderer *renderer = NULL; // 渲染 SDL_Texture *texture = NULL; // 纹理 SDL_Thread *timer_thread = NULL; // 请求刷新线程 uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV // 分辨率 // 1. YUV的分辨率 int video_width = YUV_WIDTH; int video_height = YUV_HEIGHT; // 2.显示窗口的分辨率 int win_width = YUV_WIDTH; int win_height = YUV_WIDTH; // YUV文件句柄 FILE *video_fd = NULL; const char *yuv_path = "yuv420p_320x240.yuv"; //读取数据后先把放到buffer里面 size_t video_buff_len = 0; uint8_t *video_buf = NULL; // 测试的文件是YUV420P格式 uint32_t y_frame_len = video_width * video_height; uint32_t u_frame_len = video_width * video_height / 4; uint32_t v_frame_len = video_width * video_height / 4; uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len; //创建窗口 window = SDL_CreateWindow("Simplest YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_width, video_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if(!window) { fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError()); goto _FAIL; } // 基于窗口创建渲染器 renderer = SDL_CreateRenderer(window, -1, 0); // 基于渲染器创建纹理 texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, video_width, video_height); // 分配空间 video_buf = (uint8_t*)malloc(yuv_frame_len); if(!video_buf) { fprintf(stderr, "Failed to alloce yuv frame space!\n"); goto _FAIL; } // 打开YUV文件 video_fd = fopen(yuv_path, "rb"); if( !video_fd ) { fprintf(stderr, "Failed to open yuv file\n"); goto _FAIL; } // 创建请求刷新线程 timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL); while (1) { // 收取SDL系统里面的事件 SDL_WaitEvent(&event); if(event.type == REFRESH_EVENT) // 画面刷新事件 { video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd); if(video_buff_len <= 0) { fprintf(stderr, "Failed to read data from yuv file!\n"); goto _FAIL; } // 设置纹理的数据 video_width = 320, plane SDL_UpdateTexture(texture, NULL, video_buf, video_width); // 显示区域,可以通过修改w和h进行缩放 rect.x = 0; rect.y = 0; float w_ratio = win_width * 1.0 /video_width; float h_ratio = win_height * 1.0 /video_height; // 320x240 怎么保持原视频的宽高比例 rect.w = video_width * w_ratio; rect.h = video_height * h_ratio; // 清除当前显示 SDL_RenderClear(renderer); // 将纹理的数据拷贝给渲染器 SDL_RenderCopy(renderer, texture, NULL, &rect); // 显示 SDL_RenderPresent(renderer); } else if(event.type == SDL_WINDOWEVENT) { //If Resize SDL_GetWindowSize(window, &win_width, &win_height); printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width, win_height ); } else if(event.type == SDL_QUIT) //退出事件 { s_thread_exit = 1; } else if(event.type == QUIT_EVENT) { break; } }_FAIL: s_thread_exit = 1; // 保证线程能够退出 // 释放资源 if(timer_thread) SDL_WaitThread(timer_thread, NULL); // 等待线程退出 if(video_buf) free(video_buf); if(video_fd) fclose(video_fd); if(texture) SDL_DestroyTexture(texture); if(renderer) SDL_DestroyRenderer(renderer); if(window) SDL_DestroyWindow(window); SDL_Quit(); return 0;}
SDL播放音频PCM-打开音频设备
- 打开音频设备:
int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);
- desired:期望的参数。
- obtained:实际音频设备的参数,一般情况下设置为NULL即可。
- SDL_AudioSpec:
typedef struct SDL_AudioSpec { int freq; // 音频采样率 SDL_AudioFormat format; // 音频数据格式 Uint8 channels; // 声道数: 1 单声道, 2 立体声 Uint8 silence; // 设置静音的值, 因为声音采样是有符号的, 所以0当然就是这个值 Uint16 samples; // 音频缓冲区中的采样个数,要求必须是2的n次 Uint16 padding; // 考虑到兼容性的一个参数 Uint32 size; // 音频缓冲区的大小,以字节为单位 SDL_AudioCallback callback; // 填充音频缓冲区的回调函数 void *userdata; // 用户自定义的数据} SDL_AudioSpec;
- SDL_AudioCallback:
- userdata: SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
- stream:该指针指向需要填充的音频缓冲区。
- len:音频缓冲区的大小(以字节为单位) 1024*2*2。
- 播放音频数据:
- 当pause_on设置为0的时候即可开始播放音频数据。
- 设置为1的时候,将会播放静音的值。
void SDLCALL SDL_PauseAudio(int pause_on)
- 示例代码:
/** * SDL2播放PCM * 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图 * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层 * API。 * 测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2 * * 函数调用步骤如下: * * [初始化] * SDL_Init(): 初始化SDL。 * SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。 * SDL_PauseAudio(): 播放音频数据。 * * [循环播放数据] * SDL_Delay(): 延时等待播放完成。 * */#include <stdio.h>#include <SDL.h>// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例#define PCM_BUFFER_SIZE (1024*2*2*2)// 音频PCM数据缓存static Uint8 *s_audio_buf = NULL;// 目前读取的位置static Uint8 *s_audio_pos = NULL;// 缓存结束位置static Uint8 *s_audio_end = NULL;//音频设备回调函数void fill_audio_pcm(void *udata, Uint8 *stream, int len){ SDL_memset(stream, 0, len); if(s_audio_pos >= s_audio_end) // 数据读取完毕 { return; } // 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少) int remain_buffer_len = s_audio_end - s_audio_pos; len = (len < remain_buffer_len) ? len : remain_buffer_len; // 拷贝数据到stream并调整音量 SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME/8); printf("len = %d\n", len); s_audio_pos += len; // 移动缓存指针}// 提取PCM文件// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm// 测试PCM文件// ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm#undef mainint main(int argc, char *argv[]){ int ret = -1; FILE *audio_fd = NULL; SDL_AudioSpec spec; const char *path = "44100_16bit_2ch.pcm"; // 每次缓存的长度 size_t read_buffer_len = 0; //SDL initialize if(SDL_Init(SDL_INIT_AUDIO)) // 支持AUDIO { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); return ret; } //打开PCM文件 audio_fd = fopen(path, "rb"); if(!audio_fd) { fprintf(stderr, "Failed to open pcm file!\n"); goto _FAIL; } s_audio_buf = (uint8_t *)malloc(PCM_BUFFER_SIZE); // 音频参数设置SDL_AudioSpec spec.freq = 44100; // 采样频率 spec.format = AUDIO_S16SYS; // 采样点格式 spec.channels = 2; // 2通道 spec.silence = 0; spec.samples = 1024; // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samples spec.callback = fill_audio_pcm; // 回调函数 spec.userdata = NULL; //打开音频设备 if(SDL_OpenAudio(&spec, NULL)) { fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError()); goto _FAIL; } //play audio SDL_PauseAudio(0); int data_count = 0; while(1) { // 从文件读取PCM数据 read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd); if(read_buffer_len == 0) { break; } data_count += read_buffer_len; // 统计读取的数据总字节数 printf("now playing %10d bytes data.\n",data_count); s_audio_end = s_audio_buf + read_buffer_len; // 更新buffer的结束位置 s_audio_pos = s_audio_buf; // 更新buffer的起始位置 //the main thread wait for a moment while(s_audio_pos < s_audio_end) { SDL_Delay(10); // 等待PCM数据消耗 } } printf("play PCM finish\n"); // 关闭音频设备 SDL_CloseAudio();_FAIL: //release some resources if(s_audio_buf) free(s_audio_buf); if(audio_fd) fclose(audio_fd); //quit SDL SDL_Quit(); return 0;}
发表评论
最新留言
能坚持,总会有不一样的收获!
[***.219.124.196]2025年03月22日 20时22分57秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Java中Map的用法详解
2019-03-04
Java注解全面总结
2019-03-04
base64编码字符串和图片的互转
2019-03-04
汉字转为拼音
2019-03-04
Python+Opencv识别视频统计人数
2019-03-04
python 记录下Python开发环境的安装配置
2019-03-04
大佬龟叔写的一个无聊程序
2019-03-04
linux 下安装kolla报错 提示Cannot uninstall requests
2019-03-04
Linux MySQL的socket文件存在位置更改
2019-03-04
Linux RPM和yum命令的使用技巧
2019-03-04
写博客常用的字体颜色(待续)
2019-03-04
C++ throw、try、catch、noexcept
2019-03-04
vim之vim滚屏与跳转
2019-03-04
C指针之函数指针与typedef
2019-03-04
CentOS8 字体大小调整
2019-03-04
Go 小数类型/浮点型的使用
2019-03-04
设计模式之组合模式
2019-03-04
设计模式之外观模式
2019-03-04