#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include<libswscale/swscale.h>
}
#include <SDL.h>
using namespace std;
#define YUV_FORMAT SDL_PIXELFORMAT_IYUV
//解码后的数据
static AVFrame* frame = NULL;
//解码前的数据
static AVPacket pkt;
SDL_Rect rect; // 矩形
SDL_Window* window = NULL; // 窗口
SDL_Renderer* renderer = NULL; // 渲染
SDL_Texture* texture = NULL; // 纹理
uint32_t pixformat = YUV_FORMAT; // YUV420P,即是SDL_PIXELFORMAT_IYUV
uint8_t* video_buf = NULL;
size_t video_buff_len = 0;
uint32_t yuv_frame_len = 0;
//视频数据的分辨率
int video_width = 1920;
int video_height = 1080;
//窗口的分辨率
int win_width = 1920;
int win_height = 1080;
void sdl_init()
{
SDL_Init(SDL_INIT_VIDEO);
//读取到的帧先放到缓冲区里面
video_buff_len = 0;
video_buf = NULL;
//该ts文件的流是 YUV420P格式 4个Y分量对应一组uv分量 这里一帧的长度就是yuv_frame_len 为了逻辑清晰才这么写 不然不这么写
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;
yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;
//创建窗口
window = SDL_CreateWindow("My First Player",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
win_width, win_height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
// 基于窗口创建渲染器
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);
}
#undef main
int main()
{
//初始化sdl
sdl_init();
//解码后的数据要放置的目的地
FILE* dst_file=NULL;
//流的索引
int stream_index=0;
//流的结构体指针 包括一条流的很多信息
AVStream* st=NULL;
//编解码器
AVCodec* dec = NULL;
//编解码器上下文
AVCodecContext* dec_ctx=NULL;
//申请一个内置的字典? 干啥
AVDictionary* opts = NULL;
//复用格式上下文
AVFormatContext* fmt_cxt = NULL;
//fmt_cxt = avformat_alloc_context();
//第一个参数复用格式上下文 第二个参数 视频地址 第三个参数 你希望的视频封装格式 如果设置为空自动检测视频封装格式 最后一个参数可以设置打开码流前的各种参数 比如探测时间 最大延时等
avformat_open_input(&fmt_cxt, "D:\\vid-hd-f201119101706.ts", NULL, NULL);
//给这个上下文中的stream这些字段赋值 赋值之后才能继续转换
avformat_find_stream_info(fmt_cxt, NULL);
//找到这条流对应的索引 ??为啥要这样干 最大的疑惑这里
stream_index = av_find_best_stream(fmt_cxt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
//根据索引来拿到对应的流
st = fmt_cxt->streams[stream_index];
//给编码器上下文分配内存空间
avcodec_alloc_context3(dec);
//根据编码器ID来获取对应的编解码器 得到对应的编解码器过后就可以给编解码器上下文赋值了
dec = avcodec_find_decoder(st->codecpar->codec_id);
//给编解码器上下文分配内存空间
dec_ctx = avcodec_alloc_context3(dec);
//把流中与编码器相关的信息赋值给编解码器上下文
avcodec_parameters_to_context(dec_ctx, st->codecpar);
//最后 打开编解码器 用刚刚的编解码器上下文去打开 因为我们没有设置任何选项 所以填个空的字典就行了
avcodec_open2(dec_ctx, dec, &opts);
//至此我们已经根据复用格式和流信息打开了对应的编解码器 接下来就是解码
//首先打开目的地文件 以可读可写打开
dst_file = fopen("D:\\11.txt", "wb");
//给解码后的数据分配内存
frame = av_frame_alloc();
//初始化解码前的结构体
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
//写的read frame其实是read packet 从流中读取packet
//解复用和编解码不一样 不用打开解复用器 直接用解复用上下文就可以解复用
//只要还能读出一个packet
while (av_read_frame(fmt_cxt, &pkt) >= 0)
{
int ret = 0;
//解码 packet发送到编解码器解码
ret = avcodec_send_packet(dec_ctx, &pkt);
//发送成功
while (ret >= 0) {
//收取一帧 已经解码完成的帧
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret < 0) {
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
break;
break;
}
//像素转换
//sws啥意思 slice啥意思 Stride啥意思
auto img_convert_ctx = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format,
frame->width, frame->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
auto tmp_frame = frame;
//
sws_scale(img_convert_ctx, (const unsigned char* const*)tmp_frame->data,
tmp_frame->linesize, 0, tmp_frame->height,
frame->data, frame->linesize);//avctx->height改成tmp_frame->height
sws_freeContext(img_convert_ctx);
//因为这个视频文件的YUV存储格式为 nv21 YUV420sp 就是
SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
// 显示区域,可以通过修改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);
//把pkt申请在堆区的资源释放掉 下一次av_read_frame的时候还会继续申请
av_frame_unref(frame);
av_packet_unref(&pkt);
}
SDL_Delay(40);
}
return 0;
}
ffmpeg+sdl 实现播放 注释最全 最简
最新推荐文章于 2023-08-04 16:31:16 发布