参考100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)
平台环境:windows VS 2022
#pragma comment(lib, "legacy_stdio_definitions.lib") //此为添加的代码
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
FILE __iob_func[3] = { *stdin,*stdout,*stderr };
};
以及在
项目->项目属性->链接器->命令行,在右侧其他选项中添加“/SAFESEH:NO”,这样就不会再报错了。
在代码中引入相关的FFmpeg头文件,并调用初始化函数。例如:
av_register_all();
avcodec_register_all();
avformat_network_init();
使用avformat_open_input,avformat_open_input 函数是FFmpeg(一个开源的多媒体处理库)中的一个函数,用于打开音视频文件或者网络流,并将其封装格式的相关信息填充到一个 AVFormatContext 结构体中。这个函数通常是在处理音视频文件时的初始步骤之一。
AVFormatContext *pFormatCtx;
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
参数解释:
ps: 用于存储 AVFormatContext 结构体指针的指针。AVFormatContext 是一个保存音视频文件或流相关信息的结构体,就是句柄。
url: 要打开的音视频文件或者网络流的URL。
fmt: 指定输入格式。通常可以设置为 NULL,表示由FFmpeg自动检测输入格式。
options: 一个指向 AVDictionary 结构体指针的指针,用于传递附加的选项。可以为 NULL,表示没有额外选项。
使用 avformat_find_stream_info函数,avformat_find_stream_info 函数是FFmpeg库中用于获取音视频流详细信息的函数。在打开音视频文件或者网络流后,通常需要调用这个函数来获取音视频流的相关信息,如编码格式、分辨率、帧率等。
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
参数解释:
ic: AVFormatContext 结构体,它包含了音视频文件或者网络流的相关信息,通常是通过 avformat_open_input 打开的。
options: 一个指向 AVDictionary 结构体指针的指针,用于传递附加的选项。可以为 NULL,表示没有额外选项。
函数返回值是一个整数,表示函数执行的结果。通常,如果函数成功执行,则返回 0,否则返回一个负数表示错误。
通过遍历流信息,找到视频流的索引,然后获取视频解码器的上下文。
int videoStreamIndex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStreamIndex]->codecpar);
使用avcodec_find_decoder函数查找合适的解码器,并使用avcodec_open2打开解码器。
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open2(pCodecCtx, pCodec, NULL);
创建用于存储解码后的帧的AVFrame对象以及用于存储解码前的数据包的AVPacket对象。
AVFrame *pFrame = av_frame_alloc();
AVPacket *pPacket = av_packet_alloc();
使用循环读取每个数据包,将数据包发送到解码器进行解码,得到解码后的帧。
while (av_read_frame(pFormatCtx, pPacket) >= 0) {
if (pPacket->stream_index == videoStreamIndex) {
avcodec_receive_frame(pCodecCtx, pFrame);
// 处理解码后的帧(显示、保存等)
}
av_packet_unref(pPacket);
}
avformat_close_input(&pFormatCtx);
avcodec_free_context(&pCodecCtx);
av_frame_free(&pFrame);
av_packet_free(&pPacket);
本例子使用FFmpeg库解码视频文件,并将解码后的帧保存为PPM图像文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#define IN_FILE_PATH "input_video.mp4"
#define OUT_FILE_PATH "output_frame.ppm"
int main() {
AVFormatContext *pFormatCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVPacket packet;
struct SwsContext *pSwsCtx = NULL;
// Register all formats and codecs
av_register_all();
// Open input file
if (avformat_open_input(&pFormatCtx, IN_FILE_PATH, NULL, NULL) != 0) {
fprintf(stderr, "Could not open input file\n");
return -1;
}
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
return -1;
}
// Find the first video stream
int videoStream = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
if (videoStream == -1) {
fprintf(stderr, "Could not find a video stream\n");
return -1;
}
// Get a pointer to the codec context for the video stream
pCodec = avcodec_find_decoder(pFormatCtx->streams[videoStream]->codecpar->codec_id);
if (pCodec == NULL) {
fprintf(stderr, "Unsupported codec\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar) < 0) {
fprintf(stderr, "Failed to copy codec parameters to codec context\n");
return -1;
}
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
return -1;
}
// Allocate video frame
pFrame = av_frame_alloc();
// Setup scaler context to convert video frames to RGB
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, NULL, NULL, NULL);
if (pSwsCtx == NULL) {
fprintf(stderr, "Failed to allocate SwsContext\n");
return -1;
}
// Open output file for writing PPM
FILE *ppmFile = fopen(OUT_FILE_PATH, "wb");
if (ppmFile == NULL) {
fprintf(stderr, "Could not open output file\n");
return -1;
}
// Read frames and save as PPM
while (av_read_frame(pFormatCtx, &packet) >= 0) {
if (packet.stream_index == videoStream) {
// Decode video frame
int response = avcodec_send_packet(pCodecCtx, &packet);
if (response < 0) {
fprintf(stderr, "Error decoding frame (avcodec_send_packet)\n");
break;
}
while (response >= 0) {
response = avcodec_receive_frame(pCodecCtx, pFrame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
fprintf(stderr, "Error decoding frame (avcodec_receive_frame)\n");
break;
}
// Convert the frame to RGB
uint8_t *buffer = NULL;
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);
buffer = av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(pFrame->data, pFrame->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);
sws_scale(pSwsCtx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrame->data, pFrame->linesize);
// Save the frame as PPM
fprintf(ppmFile, "P6\n%d %d\n255\n", pCodecCtx->width, pCodecCtx->height);
fwrite(buffer, 1, numBytes, ppmFile);
av_free(buffer);
}
}
av_packet_unref(&packet);
}
// Clean up
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(pSwsCtx);
fclose(ppmFile);
return 0;
}