好看的網(wǎng)站模板2022當(dāng)下社會(huì)熱點(diǎn)話題
基于ffmpeg和sdl2的簡(jiǎn)單視頻播放器制作
- 前言
- 一、視頻播放器開發(fā)的基礎(chǔ)
- 1.1 視頻播放原理
- 1.2 開發(fā)所需的庫
- 二、FFmpeg庫詳解
- 2.1 FFmpeg庫的組成
- 2.2 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
- 2.3 打開視頻文件并獲取流信息
- 2.4 查找視頻流和解碼器
- 2.5 初始化解碼器
- 三、SDL庫詳解
- 3.1 SDL庫的功能
- 3.2 初始化SDL
- 3.3 創(chuàng)建窗口、渲染器和紋理
- 3.4 事件處理
- 四、視頻播放流程
- 4.1 讀取和解碼視頻幀
- 4.2 時(shí)間同步
- 4.3 圖像格式轉(zhuǎn)換與渲染
- 五、資源清理
- 六、完整代碼示例
- 七、總結(jié)與展望
前言
本文將簡(jiǎn)單探討視頻播放器的開發(fā)過程,通過一個(gè)完整的代碼示例,帶你領(lǐng)略從打開視頻文件到播放視頻畫面的每一個(gè)關(guān)鍵步驟。
一、視頻播放器開發(fā)的基礎(chǔ)
1.1 視頻播放原理
視頻,本質(zhì)上是一系列連續(xù)的圖像幀快速播放所形成的視覺效果。在數(shù)字視頻中,這些圖像幀被編碼壓縮以減小文件大小,便于存儲(chǔ)和傳輸。常見的視頻編碼格式有H.264、H.265等。同時(shí),視頻文件通常還包含音頻數(shù)據(jù),音頻也經(jīng)過特定的編碼方式,如AAC、MP3等。
當(dāng)我們播放視頻時(shí),播放器需要執(zhí)行以下主要步驟:
- 解封裝:從視頻文件中分離出視頻流和音頻流。視頻文件通常采用某種封裝格式,如MP4、AVI等,這些格式將視頻和音頻數(shù)據(jù)按照一定的結(jié)構(gòu)組織在一起。
- 解碼:對(duì)分離出的視頻流和音頻流進(jìn)行解碼,將壓縮的數(shù)據(jù)還原為原始的圖像幀和音頻樣本。這需要使用相應(yīng)的解碼器,不同的編碼格式需要不同的解碼器。
- 渲染:將解碼后的圖像幀顯示在屏幕上,同時(shí)將音頻樣本通過音頻設(shè)備播放出來。這涉及到圖形渲染和音頻輸出的相關(guān)技術(shù)。
1.2 開發(fā)所需的庫
在開發(fā)視頻播放器時(shí),我們需要借助一些強(qiáng)大的開源庫來簡(jiǎn)化開發(fā)過程。在本文的示例中,我們主要使用了以下兩個(gè)庫:
- FFmpeg:這是一個(gè)功能強(qiáng)大的開源多媒體框架,提供了豐富的工具和函數(shù),用于處理多媒體文件的解封裝、解碼、編碼等操作。它支持幾乎所有常見的多媒體格式,并且具有高效的性能。
- SDL (Simple DirectMedia Layer):這是一個(gè)跨平臺(tái)的多媒體開發(fā)庫,專注于提供硬件抽象層,用于創(chuàng)建窗口、渲染圖形、播放音頻以及處理輸入事件等。它使得我們能夠在不同的操作系統(tǒng)上輕松實(shí)現(xiàn)一致的多媒體交互功能。
二、FFmpeg庫詳解
2.1 FFmpeg庫的組成
FFmpeg庫由多個(gè)模塊組成,每個(gè)模塊都有其特定的功能:
- libavformat:負(fù)責(zé)處理多媒體文件的格式,包括解封裝和封裝操作。它能夠識(shí)別各種常見的視頻和音頻封裝格式,如MP4、AVI、FLV等,并從中提取出視頻流和音頻流。
- libavcodec:這是FFmpeg的核心編解碼模塊,支持眾多的音頻和視頻編碼格式。它包含了各種解碼器和編碼器,能夠?qū)嚎s的多媒體數(shù)據(jù)進(jìn)行解碼或編碼操作。
- libavutil:提供了一系列通用的工具函數(shù)和數(shù)據(jù)結(jié)構(gòu),如內(nèi)存管理、錯(cuò)誤處理、數(shù)學(xué)運(yùn)算等。這些工具函數(shù)在整個(gè)FFmpeg庫的其他模塊中被廣泛使用。
- libswscale:用于圖像的縮放和格式轉(zhuǎn)換。在視頻播放過程中,由于解碼后的圖像格式可能與顯示設(shè)備所需的格式不一致,需要使用該模塊進(jìn)行轉(zhuǎn)換。
- libswresample:主要用于音頻的重采樣和格式轉(zhuǎn)換。它可以將音頻數(shù)據(jù)從一種采樣率、聲道數(shù)或樣本格式轉(zhuǎn)換為另一種,以適應(yīng)不同的音頻輸出設(shè)備。
2.2 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
- AVFormatContext:這個(gè)結(jié)構(gòu)體是FFmpeg中用于管理多媒體文件格式的上下文。它包含了文件的各種信息,如流的數(shù)量、每個(gè)流的參數(shù)等。在打開視頻文件時(shí),我們會(huì)創(chuàng)建一個(gè)AVFormatContext對(duì)象,并使用它來讀取文件的信息和解封裝數(shù)據(jù)。
- AVCodecContext:代表編解碼器的上下文,包含了編解碼所需的各種參數(shù),如編碼格式、分辨率、幀率等。在找到合適的解碼器后,我們需要?jiǎng)?chuàng)建一個(gè)AVCodecContext對(duì)象,并將其與解碼器進(jìn)行關(guān)聯(lián)。
- AVFrame:用于存儲(chǔ)解碼后的音頻或視頻數(shù)據(jù)。對(duì)于視頻來說,它包含了一幀圖像的像素?cái)?shù)據(jù);對(duì)于音頻來說,它包含了音頻樣本數(shù)據(jù)。
- AVPacket:用于存儲(chǔ)從文件中讀取的壓縮數(shù)據(jù),這些數(shù)據(jù)在經(jīng)過解封裝后以AVPacket的形式存在,然后被傳遞給解碼器進(jìn)行解碼。
2.3 打開視頻文件并獲取流信息
在我們的代碼示例中,首先需要打開視頻文件并獲取其流信息:
AVFormatContext* fmt_ctx = NULL;
std::string file_path = "F:/QT/mp4_flv/x.mp4";// 打開視頻文件
int ret = avformat_open_input(&fmt_ctx, file_path.c_str(), NULL, NULL);
if (ret < 0) {handle_ffmpeg_error(ret, "Failed to open video file.");return -1;
}// 讀取視頻流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {handle_ffmpeg_error(ret, "Error in obtaining video stream information.");avformat_close_input(&fmt_ctx);return -1;
}
這里,avformat_open_input
函數(shù)用于打開指定路徑的視頻文件,并將文件信息存儲(chǔ)在fmt_ctx
中。如果打開失敗,會(huì)調(diào)用handle_ffmpeg_error
函數(shù)進(jìn)行錯(cuò)誤處理。接著,avformat_find_stream_info
函數(shù)用于讀取視頻文件中的流信息,包括視頻流和音頻流的參數(shù)等。同樣,如果讀取失敗,也會(huì)進(jìn)行相應(yīng)的錯(cuò)誤處理。
2.4 查找視頻流和解碼器
const AVCodec* codec = NULL;
int video_stream_idx = -1;
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_idx = i;codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);if (!codec) {fprintf(stderr, "Video decoder not found\n");avformat_close_input(&fmt_ctx);return -1;}break;}
}
這段代碼通過遍歷fmt_ctx
中的所有流,找到類型為視頻流(AVMEDIA_TYPE_VIDEO
)的流,并獲取其索引video_stream_idx
。然后,根據(jù)流的編碼ID,使用avcodec_find_decoder
函數(shù)查找對(duì)應(yīng)的解碼器。如果找不到解碼器,會(huì)輸出錯(cuò)誤信息并關(guān)閉文件。
2.5 初始化解碼器
找到解碼器后,需要對(duì)其進(jìn)行初始化:
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {fprintf(stderr, "Decoder context allocation failed\n");avformat_close_input(&fmt_ctx);return -1;
}ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);
if (ret < 0) {handle_ffmpeg_error(ret, "Copying codec parameters failed!");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}ret = avcodec_open2(codec_ctx, codec, NULL);
if (ret < 0) {handle_ffmpeg_error(ret, "Decoder open failed!");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}
首先,使用avcodec_alloc_context3
函數(shù)分配一個(gè)AVCodecContext
對(duì)象。然后,將流的編碼參數(shù)復(fù)制到codec_ctx
中,最后使用avcodec_open2
函數(shù)打開解碼器。每一步操作都進(jìn)行了錯(cuò)誤處理,確保解碼器能夠正確初始化。
三、SDL庫詳解
3.1 SDL庫的功能
SDL庫主要用于創(chuàng)建圖形窗口、渲染圖像以及處理用戶輸入事件。它提供了一系列簡(jiǎn)單易用的函數(shù),使得我們能夠在不同的操作系統(tǒng)上實(shí)現(xiàn)一致的圖形界面和交互功能。
- 窗口管理:SDL可以創(chuàng)建和管理窗口,設(shè)置窗口的大小、位置、標(biāo)題等屬性。它還支持窗口的最小化、最大化、關(guān)閉等操作。
- 圖形渲染:提供了多種渲染方式,包括軟件渲染和硬件加速渲染。可以將圖像數(shù)據(jù)渲染到窗口上,實(shí)現(xiàn)視頻畫面的顯示。
- 事件處理:能夠捕獲和處理各種用戶輸入事件,如鼠標(biāo)點(diǎn)擊、鍵盤按鍵、窗口關(guān)閉等事件,使得我們的程序能夠響應(yīng)用戶的操作。
3.2 初始化SDL
在使用SDL之前,需要先對(duì)其進(jìn)行初始化:
if (SDL_Init(SDL_INIT_VIDEO) < 0) {fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}
這里使用SDL_Init
函數(shù)初始化SDL庫的視頻子系統(tǒng)。如果初始化失敗,會(huì)輸出錯(cuò)誤信息并釋放之前分配的資源。
3.3 創(chuàng)建窗口、渲染器和紋理
SDL_Window* window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);
if (!window) {fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {fprintf(stderr, "Renderer could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,codec_ctx->width, codec_ctx->height);
if (!texture) {fprintf(stderr, "Texture could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}
這段代碼依次創(chuàng)建了窗口、渲染器和紋理。SDL_CreateWindow
函數(shù)用于創(chuàng)建一個(gè)指定大小和標(biāo)題的窗口。SDL_CreateRenderer
函數(shù)創(chuàng)建一個(gè)渲染器,用于將圖像渲染到窗口上,這里使用了硬件加速渲染(SDL_RENDERER_ACCELERATED
)。最后,SDL_CreateTexture
函數(shù)創(chuàng)建一個(gè)紋理,用于存儲(chǔ)視頻圖像數(shù)據(jù),以便后續(xù)渲染。同樣,每一步創(chuàng)建操作都進(jìn)行了錯(cuò)誤處理,如果創(chuàng)建失敗,會(huì)釋放之前創(chuàng)建的資源。
3.4 事件處理
在視頻播放過程中,需要處理用戶的輸入事件,如關(guān)閉窗口事件:
SDL_Event event;
while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {goto cleanup;}
}
這里使用SDL_PollEvent
函數(shù)不斷檢查是否有事件發(fā)生。如果檢測(cè)到SDL_QUIT
事件(即用戶點(diǎn)擊了窗口的關(guān)閉按鈕),則跳轉(zhuǎn)到cleanup
標(biāo)簽處,進(jìn)行資源清理操作。
四、視頻播放流程
4.1 讀取和解碼視頻幀
在初始化完成后,進(jìn)入視頻播放的主循環(huán),不斷從視頻文件中讀取數(shù)據(jù)包并進(jìn)行解碼:
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {if (pkt.stream_index == video_stream_idx) {ret = avcodec_send_packet(codec_ctx, &pkt);if (ret < 0) {handle_ffmpeg_error(ret, "send data decoder error.");av_packet_unref(&pkt);continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {// 處理解碼后的幀}else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}}}av_packet_unref(&pkt);
}
在循環(huán)中,使用av_read_frame
函數(shù)從視頻文件中讀取一個(gè)數(shù)據(jù)包pkt
。如果數(shù)據(jù)包屬于視頻流(通過pkt.stream_index
判斷),則將其發(fā)送給解碼器進(jìn)行解碼。avcodec_send_packet
函數(shù)將數(shù)據(jù)包發(fā)送給解碼器,avcodec_receive_frame
函數(shù)從解碼器中接收解碼后的幀。如果解碼成功(ret == 0
),則可以對(duì)解碼后的幀進(jìn)行進(jìn)一步處理;如果解碼器需要更多數(shù)據(jù)(ret == AVERROR(EAGAIN)
)或已經(jīng)到達(dá)文件末尾(ret == AVERROR_EOF
),則退出內(nèi)層循環(huán)。每次處理完數(shù)據(jù)包后,使用av_packet_unref
函數(shù)釋放數(shù)據(jù)包的引用。
4.2 時(shí)間同步
為了保證視頻播放的流暢性和音頻視頻的同步,需要進(jìn)行時(shí)間同步:
int64_t start_time = av_gettime();// 在解碼幀的處理部分
int64_t pts = frame->pts;
if (pts == AV_NOPTS_VALUE) {pts = av_rescale_q(pkt.dts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });
}
else {pts = av_rescale_q(frame->pts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });
}int64_t now = av_gettime();
if (pts > now - start_time) {SDL_Delay((pts - (now - start_time)) / 1000);
}
這里使用av_gettime
函數(shù)獲取當(dāng)前時(shí)間。通過幀的顯示時(shí)間戳(pts
)和當(dāng)前時(shí)間的比較,計(jì)算出需要延遲的時(shí)間,使用SDL_Delay
函數(shù)進(jìn)行延遲,以確保視頻幀按照正確的時(shí)間順序顯示。
4.3 圖像格式轉(zhuǎn)換與渲染
解碼后的幀需要進(jìn)行格式轉(zhuǎn)換并渲染到窗口上:
SDL_Rect rect = { 0, 0, codec_ctx->width, codec_ctx->height };
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);// 在解碼幀的處理部分
SDL_UpdateYUVTexture(texture, &rect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
首先,使用sws_getContext
函數(shù)創(chuàng)建一個(gè)圖像格式轉(zhuǎn)換上下文sws_ctx
,將解碼后的幀格式轉(zhuǎn)換為適合SDL渲染的格式(這里是AV_PIX_FMT_YUV420P
)。然后,使用SDL_UpdateYUVTexture
函數(shù)將轉(zhuǎn)換后的幀數(shù)據(jù)更新到紋理中。接著,使用SDL_RenderClear
函數(shù)清空渲染器,SDL_RenderCopy
函數(shù)將紋理數(shù)據(jù)復(fù)制到渲染器上,最后使用SDL_RenderPresent
函數(shù)將渲染器的內(nèi)容顯示在窗口上。
五、資源清理
在視頻播放結(jié)束后,需要釋放所有分配的資源,以避免內(nèi)存泄漏:
cleanup:sws_freeContext(sws_ctx);SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);
這里依次釋放了圖像格式轉(zhuǎn)換上下文、紋理、渲染器、窗口、SDL庫資源、視頻幀、解碼器上下文以及視頻文件格式上下文。
六、完整代碼示例
#include <iostream>
#include <string>
#include <SDL2\SDL.h>
extern "C" {
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libavutil\avutil.h>
#include <libswscale\swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil\pixfmt.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}
#include <chrono> // 用于時(shí)間同步
#include <thread>void handle_ffmpeg_error(int ret, const char* msg) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "%s: %s\n", msg, errbuf);
}
#undef main
int main() {AVFormatContext* fmt_ctx = NULL;std::string file_path = "F:/QT/mp4_flv/x.mp4";// 打開視頻文件int ret = avformat_open_input(&fmt_ctx, file_path.c_str(), NULL, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Failed to open video file.");return -1;}// 讀取視頻流信息ret = avformat_find_stream_info(fmt_ctx, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Error in obtaining video stream information.");avformat_close_input(&fmt_ctx);return -1;}// 查找視頻流和解碼器const AVCodec* codec = NULL;int video_stream_idx = -1;for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_idx = i;codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);if (!codec) {fprintf(stderr, "Video decoder not found\n");avformat_close_input(&fmt_ctx);return -1;}break;}}AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Decoder context allocation failed\n");avformat_close_input(&fmt_ctx);return -1;}ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);if (ret < 0) {handle_ffmpeg_error(ret, "Copying codec parameters failed!");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Decoder open failed!");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}AVFrame* frame = av_frame_alloc();SwsContext* sws_ctx = NULL;if (SDL_Init(SDL_INIT_VIDEO) < 0) {fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Window* window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);if (!window) {fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);if (!renderer) {fprintf(stderr, "Renderer could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,codec_ctx->width, codec_ctx->height);if (!texture) {fprintf(stderr, "Texture could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Rect rect = { 0, 0, codec_ctx->width, codec_ctx->height };sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);int64_t start_time = av_gettime();AVPacket pkt;while (av_read_frame(fmt_ctx, &pkt) >= 0) {if (pkt.stream_index == video_stream_idx) {ret = avcodec_send_packet(codec_ctx, &pkt);if (ret < 0) {handle_ffmpeg_error(ret, "send data decoder error.");av_packet_unref(&pkt);continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {int64_t pts = frame->pts;if (pts == AV_NOPTS_VALUE) {pts = av_rescale_q(pkt.dts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });}else {pts = av_rescale_q(frame->pts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });}int64_t now = av_gettime();if (pts > now - start_time) {SDL_Delay((pts - (now - start_time)) / 1000);}SDL_UpdateYUVTexture(texture, &rect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);}else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}}}av_packet_unref(&pkt);SDL_Event event;while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {goto cleanup;}}}cleanup:sws_freeContext(sws_ctx);SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return 0;
}
七、總結(jié)與展望
通過本文的詳細(xì)介紹和代碼示例,我們深入了解了視頻播放器的開發(fā)過程。從視頻播放的基本原理,到使用FFmpeg庫進(jìn)行視頻文件的解封裝、解碼,再到利用SDL庫進(jìn)行窗口創(chuàng)建、圖形渲染和事件處理,每一個(gè)環(huán)節(jié)都緊密相扣,共同構(gòu)成了一個(gè)完整的視頻播放系統(tǒng)。
然而,這只是一個(gè)簡(jiǎn)單的視頻播放器示例,實(shí)際應(yīng)用中的視頻播放器還需要具備更多的功能和優(yōu)化。例如,支持更多的視頻和音頻格式、實(shí)現(xiàn)音頻的播放和同步、添加播放控制功能(如暫停、快進(jìn)、快退等)、優(yōu)化性能以適應(yīng)不同的硬件環(huán)境等。