前言

这篇文章记录一个简单视频播放器的开发过程,代码极其为简洁,基于ffmpeg最新版本4.1实现的。视频渲染用的SDL2.0,SDL视频渲染部分代码直接copy的雷神的最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)但是他这篇的代码对于高版本的ffmpeg已经不适配了。

运行结果

测试视频是在Best Place on the Web to Download HD Trailers下载的电影宣传片,1080p的,下面是播放的画面。

  • log记录,打印log方便调试程序,另外这个简单的小程序没有内存泄漏,从播放开始到结束一直都是100M。

结构体简单说明:

结构体:
AVFormatContext:封装格式上下文,媒体文件的处理句柄。
AVCodeContext:解码器上下文,用于存储解码器相关信息。
AVCodec:解码器结构体
AVCodecParameters:描述媒体流的详细信息,这个结构体在早期版本是没有的,加进来应该是为了解码和解封装直接的解耦合。
AVPacket: av_read_frame()的返回结果,是从AVFormatContext中读取到的信息,就叫pkt吧,一个pkt中可能只能包含一种媒体流,音频、视频、字幕等,如果是视频流可能包含一帧视频,如果是音频流可能包含多个音频帧。
AVFrame:这个结构体就是FFmpeg解码出来的实际数据了,经由av_send_packet(),和av_receive_frame()后得到的数据AVFrame,根据软硬解码的选择不同可能有不同的数据格式,常见的有yuv420p(多见于软解码),nv12,nv21等(常见于硬解码),一般的渲染播放需要rgb数据,这部分数据可以用FFmpeg的swscale来实现,但是效率较低。

API调用顺序说明

  1. avformat_open_input() //打开播放文件,可以是网络流或者本地文件
  2. avforamt_find_stream_info() //Read packets of a media file to get stream information
  3. av_find_best_stream() //找到媒体流对应的index
  4. avcodec_parameters_alloc //为AVCodecParameters分配空间
  5. avcodec_find_decoder //找到解码器
  6. avcodec_alloc_context3 //为avcodec_alloc_context3 分配空间
  7. avcodec_parameters_to_context() //Fill the codec context based on the values from the supplied codec
  8. avcodec_open2() .//打开解码器
  9. av_packet_alloc(); 为AVPacket分配空间
  10. av_frame_alloc()为AVFrame分配空间
    11 .av_read_frame()从AVFormatContext中读取AVPacket。
  11. avcodec_send_packet() //将读取的pkt发送到解码线程
  12. avcodec_receive_frame()从解码线程中取出解码后的数据AVFrame.

注意

av_packet_unref(pkt);
av_frame_unref(frame);

  • 注意一下这两个API,AVFram和AVPacket在alloc内存后呢会进入解码循环,循环中av_read_frame()会不断的读帧,每读取一个pkt,AVPacket的引用计数就会加1,同理在后面取frame的时候AVFrame的引用计数也会加1,这样就会导致内存泄漏,所以要在用完AVPacket和AVFrame后使用上面两个api使的引用计数减1,防止内存泄漏。在彻底用完之后要使用av_packet_free(&pkt);av_frame_free(&frame);来彻底释放内存。

源代码

这是第一版源码,以后有时间加入音频解码和音视频同步,再对解码解封装封装成生产者消费者模式,更符合实际生产需求。

/*
* @author wangyu
* @date 2020-08-08
* @upcwangyu@163.com
*/
#include<stdio.h>
#include <windows.h>
extern "C"
{#include<libavformat/avformat.h>
#include<libavcodec/avcodec.h>
#include<libavutil/avutil.h>
#include<libswresample/swresample.h>
#include<libswscale/swscale.h>
#include<SDL/SDL.h>
}
int main(int argc,char*argv[])
{/** SDL变量*///SDL---------------------------int screen_w = 0, screen_h = 0;SDL_Window* screen;SDL_Renderer* sdlRenderer;SDL_Texture* sdlTexture;SDL_Rect sdlRect;char url[] = "test.mov";AVFormatContext* ifmt=NULL;//打开文件int ret=avformat_open_input(&ifmt, url, NULL, NULL);if (ret < 0){printf("can not open the input file %s\n", url);return -1;}printf("open the file successed!\n");ret =avformat_find_stream_info(ifmt, NULL);if (ret < 0){printf("avformat_find_stream_info failed\n");return -1;}printf("avformat_find_stream_info successed!\n");int video_index = -1;int audio_index = -1;video_index=av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (video_index < 0){printf("can not find the video stream!\n");return - 1;}printf("the videostream index is %d\n", video_index);if (audio_index < 0){printf("can not find the audio stream!\n");}printf("the audio stream index is %d\n", audio_index);av_dump_format(ifmt, 1, url,0);//解码环节:/** 找到解码器* 分配解码器上下文空间* 解码器参数复制*/AVCodecParameters* para = avcodec_parameters_alloc();AVCodec* vcodec = avcodec_find_decoder(ifmt->streams[video_index]->codecpar->codec_id);if (!vcodec){printf(" can not find the  vidoe decoder!\n");return -1;}AVCodecContext* vctx = avcodec_alloc_context3(vcodec);avcodec_parameters_to_context(vctx, ifmt->streams[video_index]->codecpar);ret =avcodec_open2(vctx, vcodec, NULL);if (ret < 0){printf("avcodec_open2 failed!\n");return -1;}printf("avcodec_open2() successed!\n");/** SDL代码*/if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {printf("Could not initialize SDL - %s\n", SDL_GetError());return -1;}screen_w = vctx->width;screen_h = vctx->height;//SDL 2.0 Support for multiple windowsscreen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h,SDL_WINDOW_OPENGL);if (!screen) {printf("SDL: could not create window - exiting:%s\n", SDL_GetError());return -1;}sdlRenderer = SDL_CreateRenderer(screen, -1, 0);//IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);sdlRect.x = 0;sdlRect.y = 0;sdlRect.w = screen_w;sdlRect.h = screen_h;/** SDL End*/AVPacket* pkt=NULL;//av_packet_alloc中包含了av_pack_init()pkt = av_packet_alloc();AVFrame* frame = av_frame_alloc();while (1){ret=av_read_frame(ifmt, pkt);if (ret < 0){printf("read failed or the file is ended!\n");av_packet_unref(pkt);return -1;}if (pkt->stream_index != video_index){av_packet_unref(pkt);continue;}printf("pkt's size=%d, pkt's pts =%ld\n", pkt->size, pkt->pts);//开始解码ret=avcodec_send_packet(vctx, pkt);if (ret < 0){printf("avcodec_send_packet failed!\n");av_packet_unref(pkt);continue;}printf("avcodec_send_packet successed!\n");//用完packet第一时间释放内存av_packet_unref(pkt);ret = avcodec_receive_frame(vctx, frame);if (ret < 0){printf("avcodec_receive_frame failed!\n");av_packet_unref(pkt);av_frame_unref(frame);continue;}printf("avcodec_receive_frame successed!\n");printf("frame.width= %d,frame.height= %d\n",frame->width, frame->height);/** SDL     */SDL_UpdateYUVTexture(sdlTexture, &sdlRect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(sdlRenderer);SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);SDL_RenderPresent(sdlRenderer);SDL_Delay(40);//释放内存av_frame_unref(frame);/**   SDL End*///Sleep(500);//40ms}av_packet_free(&pkt);av_frame_free(&frame);//释放内存avformat_free_context(ifmt);avcodec_parameters_free(&para);avcodec_free_context(&vctx);SDL_Quit();return 0;
}

==============================================================================

源代码加入了音频解码和播放,没区分线程也没做同步,等有时间再添加进去。

/*
* @author wangyu
* @date 2020-08-08
* @upcwangyu@163.com
*/
#include<stdio.h>
#include <windows.h>
extern "C"
{#include<libavformat/avformat.h>
#include<libavcodec/avcodec.h>
#include<libavutil/avutil.h>
#include<libswresample/swresample.h>
#include<libswscale/swscale.h>
#include<SDL/SDL.h>
}
#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
//48000 * (32/8)unsigned int audioLen = 0;
unsigned char* audioChunk = NULL;
unsigned char* audioPos = NULL;void fill_audio(void* udata, Uint8* stream, int len)
{SDL_memset(stream, 0, len);if (audioLen == 0)return;len = (len > audioLen ? audioLen : len);SDL_MixAudio(stream, audioPos, len, SDL_MIX_MAXVOLUME);audioPos += len;audioLen -= len;
}int main(int argc, char* argv[])
{/** SDL变量*///SDL---------------------------uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道enum AVSampleFormat out_sample_fmt = (AVSampleFormat)AV_SAMPLE_FMT_S16; //声音格式int out_sample_rate = 44100;   //采样率int out_nb_samples = -1;int out_channels = -1;        //通道数int out_buffer_size = -1;   //输出buffunsigned char* outBuff = NULL;uint64_t in_chn_layout = -1;  //通道布局 SDL_AudioSpec wantSpec;int screen_w = 0, screen_h = 0;SDL_Window* screen;SDL_Renderer* sdlRenderer;SDL_Texture* sdlTexture;SDL_Rect sdlRect;char url[] = "test.mov";AVFormatContext* ifmt = NULL;//打开文件int ret = avformat_open_input(&ifmt, url, NULL, NULL);if (ret < 0){printf("can not open the input file %s\n", url);return -1;}printf("open the file successed!\n");ret = avformat_find_stream_info(ifmt, NULL);if (ret < 0){printf("avformat_find_stream_info failed\n");return -1;}printf("avformat_find_stream_info successed!\n");int video_index = -1;int audio_index = -1;video_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (video_index < 0){printf("can not find the video stream!\n");return -1;}printf("the videostream index is %d\n", video_index);if (audio_index < 0){printf("can not find the audio stream!\n");}printf("the audio stream index is %d\n", audio_index);av_dump_format(ifmt, 1, url, 0);//解码环节:/** 视频解码器初始化工作* 找到解码器* 分配解码器上下文空间* 解码器参数复制*/AVCodecParameters* vpara = avcodec_parameters_alloc();vpara = ifmt->streams[video_index]->codecpar;AVCodec* vcodec = avcodec_find_decoder(vpara->codec_id);//AVCodec* vcodec = avcodec_find_decoder_by_name("h264_mediacodec ");if (!vcodec){printf(" can not find the  video decoder!\n");return -1;}AVCodecContext* vctx = avcodec_alloc_context3(vcodec);avcodec_parameters_to_context(vctx, vpara);ret = avcodec_open2(vctx, vcodec, NULL);if (ret < 0){printf("avcodec_open2 failed!\n");return -1;}printf("avcodec_open2() successed!\n");/**音频解码器初始化*/AVCodecParameters* apara = avcodec_parameters_alloc();apara = ifmt->streams[audio_index]->codecpar;AVCodec* acodec = avcodec_find_decoder(apara->codec_id);if (!acodec){//这里在完善音视频同步播放后考虑只有视频的情况,即音频没有也可以正常播放printf("av_find_decoder failed!\n");return -1;}AVCodecContext* actx = avcodec_alloc_context3(acodec);avcodec_parameters_to_context(actx, apara);ret=avcodec_open2(actx, acodec, NULL);if (ret < 0){printf("audio avcodec_open2() failed!\n");return -1;}printf("audio avcdecp_open2() successed!\n");/* audio codec init finished !*//** SDL代码*/if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {printf("Could not initialize SDL - %s\n", SDL_GetError());return -1;}screen_w = vctx->width;screen_h = vctx->height;//SDL 2.0 Support for multiple windowsscreen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h,SDL_WINDOW_OPENGL);if (!screen) {printf("SDL: could not create window - exiting:%s\n", SDL_GetError());return -1;}sdlRenderer = SDL_CreateRenderer(screen, -1, 0);//IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);sdlRect.x = 0;sdlRect.y = 0;sdlRect.w = screen_w;sdlRect.h = screen_h;/** SDL Video end* SDL Audio start*///out parameterout_nb_samples = actx->frame_size;out_channels = av_get_channel_layout_nb_channels(out_chn_layout);out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);outBuff = (unsigned char*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2); //双声道printf("-------->out_buffer_size is %d\n", out_buffer_size);in_chn_layout = av_get_default_channel_layout(actx->channels);wantSpec.freq = out_sample_rate;wantSpec.format = AUDIO_S16SYS;wantSpec.channels = out_channels;wantSpec.silence = 0;wantSpec.samples = out_nb_samples;wantSpec.callback = fill_audio;wantSpec.userdata = actx;if (SDL_OpenAudio(&wantSpec, NULL) < 0){printf("can not open SDL!\n");ret = -1;return -1;}//Swrstruct SwrContext* au_convert_ctx = swr_alloc();au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,out_chn_layout,                                /*out*/out_sample_fmt,                              /*out*/out_sample_rate,                             /*out*/in_chn_layout,                                  /*in*/actx->sample_fmt,               /*in*/actx->sample_rate,               /*in*/0,NULL);swr_init(au_convert_ctx);SDL_PauseAudio(0);/*SDL Audio end*/int pts, Synpts;AVPacket* pkt = NULL;//av_packet_alloc中包含了av_pack_init()pkt = av_packet_alloc();AVFrame* frame = av_frame_alloc();bool isAudio = false;//判断去读的pkt是音频还是视频。while (1){ret = av_read_frame(ifmt, pkt);if (ret < 0){printf("read failed or the file is ended!\n");av_packet_unref(pkt);return -1;}if (pkt->stream_index == audio_index){isAudio = true;//开始解码ret = avcodec_send_packet(actx, pkt);if (ret < 0){printf("audio avcodec_send_packet failed!\n");av_packet_unref(pkt);continue;}}else if(pkt->stream_index == video_index){isAudio = false;ret = avcodec_send_packet(vctx, pkt);if (ret < 0){printf("video  avcodec_send_packet failed!\n");av_packet_unref(pkt);continue;}}else{//既不是音频流也不是视频流选择丢弃,并且释放pktcontinue;av_packet_unref(pkt);}av_packet_unref(pkt);//用完packet第一时间释放内存/** frame的接收需要注意,一般一个pkt可以解码出一帧视频或者多帧音频,* 所以对于音频我们应该多次receive,保证读取完毕。*//*音频帧*/if (isAudio){while (avcodec_receive_frame(actx, frame)==0){ret=swr_convert(au_convert_ctx, &outBuff, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)frame->data, frame->nb_samples);if (ret < 0){printf("swr_convert failed!\n");}while (audioLen > 0)SDL_Delay(1);//printf(" audio avcodec_receive_frame successed!\n");audioChunk = (unsigned char*)outBuff;audioPos = audioChunk;audioLen = out_buffer_size;Synpts = frame->pts;av_frame_unref(frame);}}/*视频帧*/else{ret = avcodec_receive_frame(vctx, frame);if (ret < 0){printf(" video avcodec_receive_frame failed!\n");av_frame_unref(frame);continue;}/** SDL*/pts = frame->pts;SDL_UpdateYUVTexture(sdlTexture, &sdlRect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(sdlRenderer);SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);SDL_RenderPresent(sdlRenderer);//SDL_Delay(40);//释放内存av_frame_unref(frame);/** SDL End*///Sleep(500);//40mscontinue;}  }av_packet_free(&pkt);av_frame_free(&frame);//释放内存avformat_free_context(ifmt);avcodec_parameters_free(&vpara);avcodec_free_context(&vctx);SDL_Quit();return 0;
}

【1】csdn 工程下载——vs打开直接即可运行

基于FFmpeg4.1的视频播放器的极简实现(音视频学习笔记四)相关推荐

  1. 《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频

    这两天开始带广播电视工程大二的暑假小学期的课程设计了.本次小学期课程内容为<基于 FFmpeg + SDL 的视频播放器的制作>,其中主要讲述了视音频开发的入门知识.由于感觉本课程的内容不 ...

  2. 音视频封装格式转换器(支持avi格式转换),基于FFmpeg4.1实现(音视频学习笔记二)

    之前参照雷霄骅博士的最简单的基于FFMPEG的封装格式转换器(无编解码)的博客和FFmpeg官网的example,实现一个简单的封装格式转换器.但是后来我发现我想从mp4格式转换成avi格式的时候会报 ...

  3. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  4. Android音视频学习系列(七) — 从0~1开发一款Android端播放器(支持多协议网络拉流本地文件)

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  5. Android音视频学习系列(八) — 基于Nginx搭建(rtmp、http)直播服务器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  6. 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  7. 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  8. 基于Django框架的视频播放器设计

    基于Django框架的视频播放器设计 前言 一.简介 二.详细实现步骤 1.路由配置 2.后台代码设计(对云盘接口的访问) 3.后台代码设计(流式视频传输) 4.前端功能设计(视频播放列表) 5.前端 ...

  9. 【基于QMediaPlayer的简易视频播放器】— 3、结合QSlider实现播放进度控制和音量控制

    基于QMediaPlayer的简易视频播放器 1.创建基本布局 2.QMediaPlayer的基本使用 3.结合QSlider实现播放进度控制和音量控制 4.重载QSlider鼠标响应事件,实现单击跳 ...

最新文章

  1. JS-匀速运动-运动停止
  2. python统计httpd 进程的内存占用百分比
  3. 【琥珀】带你用好CLIP!视觉-语言表征学习新进展
  4. IEnumerableT 接口主要成员
  5. 删除Dataframe前N行或后N行
  6. memset函数详细说明 1
  7. vue-cli2.9.6更新不了问题
  8. 根据经纬度算两点距离
  9. excel 按列拆分合并 表格操作及脚本
  10. Codeforces Round #807 (Div. 2)
  11. html悬浮客服代码,js QQ客服悬浮效果实现代码
  12. Python办公——根据Excel数据批量生成二维码
  13. 华东师大计算机专业非全日制,2018年华东师范大学非全日制研究生专业目录
  14. 关于RHCE考证的那些事
  15. 解决 Failed to fetch http://172.6.0.2/ubuntu/dists/jammy/main/binary-i386/Packages 404 Not Found问题
  16. 基于Linux 5.4.18的nvme驱动学习 - Linux相关概念 (一)
  17. JUC-05-ForkJoin,Future(了解即可)
  18. flask搜索引擎whoosh的配置
  19. Python:利用matplotlib库画各种统计图
  20. 微信小程序使用全套指南

热门文章

  1. eclipse导入工程报错Faceted Project Problem(1 item)
  2. GD25Qxxx使用笔记
  3. python 两个等长list的各对应位置元素相加+两个字典相加,相同键元素累加,不同键元素取全集
  4. JM模型I帧帧内预测流程
  5. 【LaTeX】MikTex+TexStudio安装及配置论文写作环境
  6. 无线无法解释服务器域名,科学网—Ubuntu 17.10 WIFI无线网络无法解析DNS域名的解决方法 - 徐勇刚的博文...
  7. Linux移动光标指令hkjl,使用 HPC Pack 在 Linux VM 上執行 OpenFOAM - Azure Virtual Machines | Microsoft Docs...
  8. 阿里数据库内核月报导航
  9. CMOS反相器版图设计
  10. 元宇宙如何改写人类社会生活