(音视频学习笔记):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;}

 

上一篇:(音视频学习笔记):FFmpeg库简介、常用函数及数据结构解析
下一篇:(音视频学习笔记):SDL环境搭建及应用介绍

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年03月22日 20时22分57秒