FFmpeg 深入精讲(四)SDL音视频渲染实践
1 SDL 介绍
SDL (Simple DirectMedia Layer)
由 C 语言实现的跨平台的媒体开源库
多用于开发游戏、模拟器、媒体播放器等多媒体应用领域
2 SDL 编译与安装
2.1 下载SDL源码
https://www.libsdl.org/
2.2 生成 Makefile
./configure --prefix=/usr/local
2.3 编译安装
make -j 8
sudo make install
3 使用 SDL 基本步骤
3.1 添加头文件
#include <SDL2/SDL.h>
3.2 初始化 SDL
3.3 退出 SDL
4 SDL 渲染窗口
SDL_Init();
SDL_Quit();SDL_CreateWindow()
SDL_DestoryWindow()SDL_CreateRenderer()
SDL_DestroyRenderer()SDL_RenderClear()
SDL_RenderPresent()
4.1 实例
#include <SDL2/SDL.h>
#include <stdio.h>int main(int argc, char *argv[])
{SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Init(SDL_INIT_VIDEO);window = SDL_CreateWindow("SDL2 Window", 200, 200, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS);if (!window){printf("SDL_CreateWindow Failed. \n");goto __EXIT;}renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){printf("SDL_CreateRenderer Failed. \n");goto __WINDOW;}SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);SDL_RenderClear(renderer);SDL_RenderPresent(renderer);SDL_Delay(10000);SDL_DestroyRenderer(renderer);__WINDOW:SDL_DestroyWindow(window);__EXIT:SDL_Quit();return 0;
}
5 SDL 事件基本原理
5.1 SDL 将所有事件都存放在一个队列中
5.2 所有对事件的操作,其实就是对队列的操作
5.3 SDL 事件种类
5.3.1 窗口事件
SDL_WindowEvent()
5.3.2 键盘事件
SDL_KeyboardEvent()
5.3.3 鼠标事件
SDL_MouseMotionEvent()
5.4 SDL 事件处理
SDL_PollEvent()SDL_WaitEvent()SDL_WaitEventTimeout()
#include <SDL2/SDL.h>
#include <stdio.h>int main(int argc, char *argv[])
{int quit = 1;SDL_Event event;SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);window = SDL_CreateWindow("SDL2 Window", 200, 200, 640, 480, SDL_WINDOW_SHOWN);if (!window){printf("SDL_CreateWindow Failed. \n");goto __EXIT;}renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){printf("SDL_CreateRenderer Failed. \n");goto __WINDOW;}SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);SDL_RenderClear(renderer);SDL_RenderPresent(renderer);do{SDL_WaitEvent(&event);switch (event.type){case SDL_QUIT:quit = 0;break;default://SDL_log("event type is %d", event.type);break;}} while (quit);//SDL_Delay(10000);SDL_DestroyRenderer(renderer);__WINDOW:SDL_DestroyWindow(window);__EXIT:SDL_Quit();return 0;
}
6 纹理渲染
6.1 SDL 渲染基本原理
6.2 SDL 纹理相关 API
SDL_CreateTexture()
format: YUV, RGB
access: Texture 类型, Target, StreamSDL_DestroyTexture()
6.3 SDL 渲染相关 API
SDL_SetRenderTarget()SDL_RenderCliear()SDL_RenderCopy()SDL_RenderPresent()
6.4 实例
#include <SDL2/SDL.h>
#include <stdio.h>int main(int argc, char *argv[])
{int quit = 1;SDL_Event event;SDL_Rect rect;rect.w = 30;rect.h = 30;SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Texture *texture = NULL;SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);window = SDL_CreateWindow("SDL2 Window", 200, 200, 640, 480, SDL_WINDOW_SHOWN);if (!window){printf("SDL_CreateWindow Failed. \n");goto __EXIT;}renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){SDL_Log("SDL_CreateRenderer Failed. \n");goto __WINDOW;}// SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);// SDL_RenderClear(renderer);// SDL_RenderPresent(renderer);texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,640,480);if (!texture){SDL_Log("SDL_CreateTexture Failed!\n");goto __RENDER;}do{SDL_PollEvent(&event);switch (event.type){case SDL_QUIT:quit = 0;break;default:SDL_Log("event type is %d", event.type);break;}rect.x = rand() % 600;rect.y = rand() % 450;SDL_SetRenderTarget(renderer, texture);SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);SDL_RenderClear(renderer);SDL_RenderDrawRect(renderer, &rect);SDL_SetRenderDrawColor(renderer, 255, 0, 0, 0);SDL_RenderFillRect(renderer, &rect);SDL_SetRenderTarget(renderer, NULL);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);} while (quit);//SDL_Delay(10000);SDL_DestroyTexture(texture);
__RENDER:SDL_DestroyRenderer(renderer);__WINDOW:SDL_DestroyWindow(window);__EXIT:SDL_Quit();return 0;
}
7 YUV 视频播放器
7.1 创建线程
SDL_CreateThread()
fn: 线程执行函数
name: 线程名
data: 执行函数参数
7.2 SDL 更新纹理
SDL_UpdateTextre()SDL_UpdateYUVTextre()
7.3 源码
#include <SDL2/SDL.h>
#include <stdio.h>
#include <string.h>#define REFRESH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)int thread_exit = 0;int refresh_video_timer(void *udata)
{while (!thread_exit){SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}thread_exit = 0;SDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);return 0;
}int main(int argc, char *argv[])
{int quit = 1;FILE *video_fd = NULL;SDL_Event event;SDL_Rect rect;Uint32 pixformat = 0;SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Texture *texture = NULL;SDL_Thread *timer_thread = NULL;int w_width = 608;int w_height = 368;int video_width = 608;int video_height = 368;Uint8 *video_pos = NULL;Uint8 *video_end = NULL;unsigned int remain_len = 0;size_t video_buff_len = 0;size_t blank_space_len = 0;const char *path = "1.yuv";const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;unsigned int tmp_yuv_frame_len = yuv_frame_len;if (yuv_frame_len & 0xF){tmp_yuv_frame_len = yuv_frame_len * 0xFFF0 + 0x10;}if (SDL_Init(SDL_INIT_VIDEO)){fprintf(strerror, "SDL_Init Failed - %s\n", SDL_GetError());}window = SDL_CreateWindow("YUV Player",SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w_width, w_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!window){fprintf(strerror, "SDL_CreateWindow Failed - %s\n", SDL_GetError());goto __FAIL;}renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){fprintf(strerror, "SDL_CreateRenderer Failed - %s\n", SDL_GetError());goto __FAIL;}// IYUV: Y + U + V (3 planes)// YV12: Y + V + U (3 planes)pixformat = SDL_PIXELFORMAT_IYUV;texture = SDL_CreateTexture(renderer, pixformat,SDL_TEXTUREACCESS_STREAMING,video_width,video_height);if (!texture){fprintf(strerror, "SDL_CreateTexture Failed - %s\n", SDL_GetError());goto __FAIL;}Uint8 *video_buf = (Uint8*)malloc(tmp_yuv_frame_len);if (!video_buf){fprintf(strerror, "video_buf malloc Failed \n");goto __FAIL;}video_fd = fopen(path, "rb");if (!video_fd){fprintf(strerror, "video_fd fopen Failed \n");goto __FAIL;}if ((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){fprintf(strerror, "video_fd fread Failed \n");goto __FAIL;}video_pos = video_buf;/*video_end = video_buf + video_buff_len;blank_space_len = BLOCK_SIZE - video_buff_len;*/timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);do{SDL_WaitEvent(&event);if (event.type == REFRESH_EVENT){SDL_UpdateTexture(texture, NULL, video_pos, video_width);rect.x = 0;rect.y = 0;rect.w = w_width;rect.h = w_height;SDL_RenderCopy(renderer, texture, NULL, &rect);SDL_RenderPresent(renderer);if ((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){thread_exit = 1;continue;}}else if (event.type == SDL_WINDOWEVENT){SDL_GetWindowSize(window, &w_width, &w_height);}else if (event.type == SDL_QUIT){thread_exit = 1;}else if (event.type == QUIT_EVENT){break;}} while (1);//SDL_Delay(10000);
__FAIL:if (video_buf){free(video_buf);video_buf = NULL;}if (video_fd){fclose(video_fd);video_fd = NULL;}SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();return 0;
}
8 SDL 播放音频
8.1 播放音频基本流程
8.2 播放音频的基本原则
8.2.1 声卡向你要数据而不是你主动推给声卡
8.2.2 数据的多少由音频参数决定的
8.3 SDL 音频 API
SDL_OpenAudio()
SDL_CloseAudio()SDL_PauseAudio()SDL_MixAudio()
9 PCM 播放器
#include <SDL2/SDL.h>#define BLOCK_SIZE 4096000static size_t buffer_len = 0;
static Uint8 *audio_buf = NULL;
static Uint8 *audio_pos = NULL;void read_audio_data(void *userdata, Uint8 *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 ret = -1;char *path = "1.pcm";FILE *audio_fd = NULL;SDL_AudioSpec spec = {0};if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)){SDL_Log("SDL_Init Failed.\n");return ret;}audio_fd = fopen(path, "rb");if (!audio_fd){SDL_Log("audio_fd fopen Failed.\n");goto __FAIL;}audio_buf = (Uint8*)malloc(BLOCK_SIZE);if (!audio_buf){SDL_Log("audio_buf malloc memory Failed.\n");goto __FAIL;}spec.freq = 44100;spec.channels = 2;spec.format = AUDIO_S16SYS;spec.silence = 0;spec.callback = read_audio_data;spec.userdata = NULL;if (SDL_OpenAudio(&spec, NULL)){SDL_Log("SDL_OpenAudio Failed.\n");goto __FAIL;}SDL_PauseAudio(0);do {buffer_len = fread(audio_buf, 1, BLOCK_SIZE, audio_fd);audio_pos = audio_buf;while (audio_pos < (audio_buf + buffer_len)){SDL_Delay(1);}}while (buffer_len != 0);SDL_CloseAudio();ret = 0;__FAIL:if (audio_buf){free(audio_buf);audio_buf = NULL;}if (audio_fd){fclose(audio_buf);audio_buf = NULL;}SDL_Quit();return 0;
}