背景
最近重新阅读敬爱的雷神的遗作 <<最简单的基于FFMPEG+SDL的音频播放器>>
发现当时的ffmpeg版本已经不能在vs2017编译通过了.
因此我下载了最新的ffmpeg4.3(2020-6-15), 重新实现一次.
下载相关库文件
在以下页面下载ffmpeg, 注意选择4.3 windows-32bit, dev.
注意: 考虑到减少搭建中可能遇到的坑, 因此统一选择x86.
https://ffmpeg.zeranoe.com/builds/
http://www.libsdl.org/release/SDL2-2.0.12-win32-x86.zip
使用vs2017建立一个工程
命名为FFmpegSDLPlayer, 注意将工程的配置修改为x86.
配置头文件和库
将以上文件解压后, 放置在工程目录下.
首先配置工程属性C/C++-> 常规> 附加包含目录
.\ffmpeg\include;.\SDL2\include;
然后配置工程属性 链接器 > 常规 > 附加库目录
.\ffmpeg\lib;.\SDL2\lib\x86;
最后配置 工程属性 链接器 > 输入 > 附加依赖项
avcodec.lib;avformat.lib;avutil.lib;avdevice.lib;avfilter.lib;postproc.lib;swresample.lib;swscale.lib;SDL2.lib;SDL2main.lib;%(AdditionalDependencies)
代码
代码方面, 参考着"雷神"的代码写发现有些函数被废弃了.
不得不采用新的函数, 编写的过程中遇到一些阻力, 都一一克服,
比如以前的写法每次直接读取一帧, 现在的写法是每次读取一定的字节数, 放进avcodec_send_packet,
然后从avcodec_receive_frame取出, 这里如果不仔细阅读返回值的意义, 就很容易被绕晕.
#include <stdio.h>
#include <intsafe.h>
#include <stdlib.h>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL.h"
}
#define INBUF_SIZE 4096
using namespace std;
const char filePath[] = "a.h265"; // "640.mp4"; //
bool GetVideoIndex(AVFormatContext * pFormatCtx, int &i) {
int videoIndex = -1;
for (; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}
return videoIndex;
}
static int DecodePacket(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame) {
int ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "sending a packet for decoding failed! \n");
return -1;
}
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
}
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
return -1;
}
return 1;
}
void InitSDLObject(AVFrame * pFrame,
SDL_Window * &screen, SDL_Renderer* &sdlRenderer,
SDL_Texture* &sdlTexture, SDL_Rect &sdlRect) {
screen = SDL_CreateWindow("fd-player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pFrame->width, pFrame->height, SDL_WINDOW_OPENGL);
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
sdlTexture = SDL_CreateTexture(sdlRenderer,
SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
pFrame->width, pFrame->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = pFrame->width;
sdlRect.h = pFrame->height;
}
void ShowFrameInSDL(AVFrame *& pFrame,
SDL_Window * &screen, SDL_Texture*& sdlTexture,
SDL_Renderer*& sdlRenderer, SDL_Rect& sdlRect) {
if (sdlRenderer == nullptr) {
InitSDLObject(pFrame, screen, sdlRenderer, sdlTexture, sdlRect);
}
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
pFrame->data[0], pFrame->linesize[0],
pFrame->data[1], pFrame->linesize[1],
pFrame->data[2], pFrame->linesize[2]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
SDL_Delay(40);
}
int main(int argc, char* argv[]) {
AVFormatContext *pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filePath, NULL, NULL) != 0) {
fprintf(stderr, "open file %s failed! \n", filePath);
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
fprintf(stderr, "find stream info failed! \n");
return -1;
}
int i = 0;
int videoIndex = GetVideoIndex(pFormatCtx, i);
if (videoIndex < 0) {
fprintf(stderr, "get video stream %d failed! \n", i);
return -1;
}
AVCodecParameters *pCodecParam = pFormatCtx->streams[videoIndex]->codecpar;
AVCodec *pCodec = avcodec_find_decoder(pCodecParam->codec_id);
if (pCodec == NULL) {
fprintf(stderr, "[Err] find decoder of videoIndex %d failed! \n", i);
return -1;
}
AVCodecParserContext *parser = av_parser_init(pCodec->id);
if (!parser) {
fprintf(stderr, "parser init failed! \n");
return -1;
}
AVCodecContext *codecContext = avcodec_alloc_context3(pCodec);
if (!codecContext) {
fprintf(stderr, "alloc context failed! \n");
return -1;
}
if (avcodec_parameters_to_context(codecContext, pCodecParam) < 0) {
fprintf(stderr, "parameters to context failed! \n");
return -1;
}
if (avcodec_open2(codecContext, pCodec, NULL) < 0) {
fprintf(stderr, "open decoder failed! \n");
return -1;
}
AVFrame *pFrame = av_frame_alloc();
AVFrame * pFrameYUV = av_frame_alloc();
AVPacket* pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "alloc packet failed! \n");
}
av_dump_format(pFormatCtx, 0, filePath, 0);
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "init sdl failed! \n");
return -1;
}
SDL_Window *screen = nullptr;
SDL_Renderer* sdlRenderer = nullptr;
SDL_Texture* sdlTexture = nullptr;
SDL_Rect sdlRect = { 0 };
uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
uint8_t *data;
FILE* f = fopen(filePath, "rb");
if (!f) {
fprintf(stderr, "open file failed failed! \n");
return -1;
}
while (!feof(f)) {
int data_size = fread(inbuf, 1, INBUF_SIZE, f);
if (!data_size){
break;
}
data = inbuf;
while (data_size > 0) {
int ret = av_parser_parse2(parser, codecContext, &pkt->data, &pkt->size,
data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "av parse failed! \n");
return -1;
}
data += ret;
data_size -= ret;
if (pkt->size) {
if (pkt->stream_index == videoIndex) {
int retd = DecodePacket(codecContext, pkt, pFrame);
if (retd == 0) continue;
if (retd == -1) return -1;
ShowFrameInSDL(pFrame, screen, sdlTexture, sdlRenderer, sdlRect);
}
}
}
}
SDL_Quit();
av_parser_close(parser);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_free_context(&codecContext);
av_packet_free(&pkt);
avformat_close_input(&pFormatCtx);
return 0;
}
FFmpeg库
1. libavutil
核心库, 公共的音视频处理操作
2. libavformat
处理文件格式和协议
3. libavcodec
编解码库, codec层
4. libavfilter
滤镜, 音频特效, 视频特效,
5. libavdevice
输出设备
6. libswresample
音频重采样
7. libswscale
格式转换, 宽高缩放
8. libpostproc
后期处理
FFmpeg常用函数
1. av_register_all
注册所有组件
2. avformat_open_input
打开输入视频文件
3. avformat_find_stream_info
获取视频文件信息
4. avcodec_find_decoder
查找解码器
5. avcodec_open1
打开解码器
6. av_read_frame
从输入文件读取一帧压缩数据
7. avcodec_decode_video2
解码一桢压缩数据
8. avcodec_close
关闭解码器
9. avformat_close_input
关闭输入视频文件
FFmpeg常用结构体
AVFormatContext // 上下文信息, 封装格式, 含AVStream []数组
AVInputFormat // 封装格式的名称, id, 扩展名
AVStream // 音频或视频的编码信息, 含AVCodecContext
AVCodecContext // 音视频的编解码详细信息, 如音频(采样率, 采样格式, 声道), 视频(图像格式, 宽高)
AVCodec // 音视频的编解码器, 名称, id,
AVPacket // 一帧编码后的数据
AVFrame // 一帧解码后的数据
FFmpeg命令
ffmpeg -i output.mp4 -i 1.ts -acodec copy -ar 48000 out2.ts
ffmpeg -i audio.mp3 -i video-with-audio.avi -map 0:0 -map 1:0 -acodec copy -vcodec copy -shortest output.avi
ffmpeg -i musicshort.wav -i movie.avi final_video.avi
ffmpeg -i music.mp3 music.wav
ffmpeg -i video.flv -vn audio.mp3
ffmpeg -i 0.ts -vn -acodec copy -absf aac_adtstoasc output.mp4
ffmpeg -i output.mp4 -i 1.txt -map_metadata 1 -c:a copy -id3v2_version 3 -write_id3v1 1 output2.mp4
ffmpeg -i out2.ts -metadata title="guo" -metadata artist="guo" -metadata track="11" -metadata date="2012" -metadata album="guo" -c:a copy -id3v2_version 3 -write_id3v1 1 -y out4.ts
我的代码库
https://github.com/gzx-miller/ffmpeg_sdl_player_vs2017
参考
http://ffmpeg.org/documentation.html
http://wiki.libsdl.org/FrontPage
https://blog.csdn.net/leixiaohua1020/article/details/10528443?utm_source=blogxgwz1