FFmpeg 深入精讲(二)FFmpeg 初级开发
1 FFmpeg 日志的使用及目录操作
1.1 FFmpeg 日志系统
#include <libavutil/log.h>av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "...%s\n", op);
1.2 常用日志级别
AV_LOG_ERROR
AV_LOG_WARNING
AV_LOG_INFO
AV_LOG_DEBUG
1.3 日志实例
#include <stdio.h>
#include <libavutil/log.h>int main(int argc, char* argv[])
{av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "Hello World: %s, %d !\n", "aaa", 10);return 0;
}// clang -g -o ffmpeg_log ffmpeg_log.c -lavutil
1.4 FFmpeg 文件的删除与重命名
avpriv_io_delete()avpriv_io_move()
#include <libavformat/avformat.h>int main(int argc, char *argv[])
{int ret;ret = avpriv_io_move("111.txt", "222.txt");if (ret < 0){av_log(NULL, AV_LOG_ERROR, "failed to rename\n");return -1;}av_log(NULL, AV_LOG_INFO, "Success to rename\n");// delete urlret = avpriv_io_delete("./mytestfile.txt");if (ret < 0){av_log(NULL, AV_LOG_ERROR, "failed to delete file mytestfile.txt\n");return -1;}av_log(NULL, AV_LOG_INFO, "Success to delete mytestfile.txt\n");return 0;
}// clang -g -o ffmpeg_del ffmpeg_file.c 'pkg-config --libs libavformat'
// pkg-config --libs libavformat
// -L/usr/local/ffmpeg/lib -lavformat
1.5 操作目录重要函数
avio_open_dir()avio_read_dir()avie_close_dir()
1.6 操作目录重要结构体
AVIODirContext操作目录的上下文AVIODirEntry目录项。用于存放文件名,文件大小等信息
1.6 ls 命令
#include <libavformat/avformat.h>
#include <libavutil/log.h>int main(int argc, char *argv[])
{int ret;AVIODirContext *ctx = NULL;AVIODirEntry *entry = NULL;av_log_set_level(AV_LOG_INFO);ret = avio_open_dir(&ctx, "./", NULL);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Cant open dir:%s\n", av_errtostr(ret));return -1;}while (1){ret = avio_read_dir(ctx, &entry);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Cant read dir:%s\n", av_errtostr(ret));goto __fail;}if (!entry){break;}av_log(NULL, AV_LOG_INFO, "%12"PRId64" %s" \n, entry->size, entry->name);avio_free_directory_entry(&entry);}__fail:avio_close_dir(&ctx);return 0;
}// clang -g -o ffmpeg_list ffmpeg_list.c 'pkg-config --libs libavutil'
// pkg-config --libs libavutil
// -L/usr/local/ffmpeg/lib -lavutil
2 介绍 FFmpeg 的基本概念及常用结构体
2.1 多媒体文件的基本概念
2.1.1 多媒体文件其实是个容器
2.1.2 在容器里有很多流 (Stream / Track)
2.1.3 每种流是由不同的编码器编码的
2.1.4 从流中读出的数据称为包
2.1.5 在一个包中包含着一个或多个帧
2.2 几个重要的结构体
AVFormatContext格式上下文AVStream节目流AVPacket数据包
2.3 FFmpeg 操作流数据的基本步骤
3 对复用/解复用及流操作的各种实战
4 FFmpeg 代码结构
5 安装配置VSCode
5.1 下载 VSCode
code.visualstudio.com
5.2 在命令行下启动 VSCode
5.3 配置 C/C++ 环境 VSCode
// tasks.json"args": ["-fdiagnostics-color=always","-g","${file}","-o","${fileDirname}/${fileBasenameNoExtension}","`pkg-config --libs --cflags libavutil`"],
//launch.json"program": "${workspaceFolder}/${fileBasenameNoExtension}","args": ["/home/dir/2018.mp4","1.aac"],"args": ["/home/magf/2018.mp4","1.h264"],
5.4 实例
#include <stdio.h>
#include <libavutil/log.h>int main(int argc, char* argv[])
{printf("Hello World!\n");av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "Hello wrold!\n");return 0;
}
5.4.1 bash编译脚本 build.sh
#!/bin/bashgcc -g -o test test.c `pkg-config --libs --cflags libavutil libavformat libavcodec libswscale`
5.4.2 cmake编译脚本 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)project(ffmpeg-basic LANGUAGES C)include_directories(${CMAKE_SOURCE_DIR}/../include)
include_directories(${CMAKE_SOURCE_DIR}/../thirdparty/ffmpeg/include)
include_directories(${CMAKE_SOURCE_DIR}/../thirdparty/sdl2/include/SDL2)set(ffmpeg_libs_dir ${CMAKE_SOURCE_DIR}/../thirdparty/ffmpeg/libs)
link_directories(${ffmpeg_libs_dir})
file(GLOB ffmpeg_dylibs ${ffmpeg_libs_dir}/*.so)set(sdl2_libs_dir ${CMAKE_SOURCE_DIR}/../thirdparty/sdl2/libs)
link_directories(${sdl2_libs_dir})
file(GLOB sdl2_dylibs ${sdl2_libs_dir}/*.so)
add_executable(ffmpeg-basic test.c)
target_link_libraries(ffmpeg-basic PRIVATE ${ffmpeg_dylibs} ${sdl2_dylibs})include(GNUInstallDirs)
install(TARGETS ffmpeg-basicLIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)// cmake .
// make
6 抽取音频数据实战
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = 0;int index = 0;// 1 处理一些参数char* src;char* dst;av_log_set_level(AV_LOG_DEBUG);if (argc < 3){av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3 \n");exit(-1);}src = argv[1];dst = argv[2];AVFormatContext *pFmtCtx = NULL;AVFormatContext *oFmtCtx = NULL;const AVOutputFormat *outFmt = NULL;AVStream *outStream = NULL;AVStream *inStream = NULL;AVPacket pkt;// 2 打开多媒体文件if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))< 0){av_log(NULL, AV_LOG_ERROR, "%d\n", ret);exit(-1);}// 3 从多媒体文件中找到音频流index = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);if (index < 0){av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio");goto _ERROR;}// 4 打开目的文件的上下文件oFmtCtx = avformat_alloc_context();;if (!oFmtCtx){av_log(NULL, AV_LOG_ERROR,"No Memory");goto _ERROR;}outFmt = av_guess_format(NULL, dst, NULL);oFmtCtx->oformat = outFmt;// 5 为目的文件,创建一个新的音频流outStream = avformat_new_stream(oFmtCtx, NULL);// 6 设置输出音频参数inStream = pFmtCtx->streams[index];avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);outStream->codecpar->codec_tag = 0;// 绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 7 写媒体文件头到目的文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 8 从源多媒体文件中读到音频数据到目的文件中while (av_read_frame(pFmtCtx, &pkt) >= 0){if (pkt.stream_index == index){pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_INF|AV_ROUND_PASS_MINMAX));pkt.dts = pkt.pts;pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);pkt.stream_index = 0;pkt.pos = -1;av_interleaved_write_frame(oFmtCtx, &pkt);av_packet_unref(&pkt);}}// 9 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10 将申请的资源释放掉_ERROR:if (pFmtCtx){avformat_close_input(&pFmtCtx);;pFmtCtx = NULL;}if (oFmtCtx->pb){avio_close(oFmtCtx->pb);}if (oFmtCtx){avformat_free_context(oFmtCtx);;oFmtCtx = NULL;}av_log(NULL, AV_LOG_INFO, "Hello wrold!\n");return 0;
}
7 抽取视频数据
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = 0;int index = 0;// 1 处理一些参数char* src;char* dst;av_log_set_level(AV_LOG_DEBUG);if (argc < 3){av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3 \n");exit(-1);}src = argv[1];dst = argv[2];AVFormatContext *pFmtCtx = NULL;AVFormatContext *oFmtCtx = NULL;const AVOutputFormat *outFmt = NULL;AVStream *outStream = NULL;AVStream *inStream = NULL;AVPacket pkt;// 2 打开多媒体文件if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))< 0){av_log(NULL, AV_LOG_ERROR, "%d\n", ret);exit(-1);}// 3 从多媒体文件中找到视频流index = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);if (index < 0){av_log(pFmtCtx, AV_LOG_ERROR, "Does not include video stream");goto _ERROR;}// 4 打开目的文件的上下文件oFmtCtx = avformat_alloc_context();;if (!oFmtCtx){av_log(NULL, AV_LOG_ERROR,"No Memory");goto _ERROR;}outFmt = av_guess_format(NULL, dst, NULL);oFmtCtx->oformat = outFmt;// 5 为目的文件,创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL);// 6 设置输出视频参数inStream = pFmtCtx->streams[index];avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);outStream->codecpar->codec_tag = 0;// 绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 7 写媒体文件头到目的文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 8 从源多媒体文件中读到视频数据到目的文件中while (av_read_frame(pFmtCtx, &pkt) >= 0){if (pkt.stream_index == index){pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_INF|AV_ROUND_PASS_MINMAX));pkt.dts = av_rescale_q_rnd(pkt.dts, inStream->time_base, outStream->time_base, (AV_ROUND_INF|AV_ROUND_PASS_MINMAX));pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);pkt.stream_index = 0;pkt.pos = -1;av_interleaved_write_frame(oFmtCtx, &pkt);av_packet_unref(&pkt);}}// 9 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10 将申请的资源释放掉_ERROR:if (pFmtCtx){avformat_close_input(&pFmtCtx);;pFmtCtx = NULL;}if (oFmtCtx->pb){avio_close(oFmtCtx->pb);}if (oFmtCtx){avformat_free_context(oFmtCtx);;oFmtCtx = NULL;}av_log(NULL, AV_LOG_INFO, "Hello wrold!\n");return 0;
}
8 多媒体文件转封装
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = 0;int index = 0;int stream_index = 0;int i = 0;// 1 处理一些参数char* src;char* dst;int *stream_map = NULL;av_log_set_level(AV_LOG_DEBUG);if (argc < 3){av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3 \n");exit(-1);}src = argv[1];dst = argv[2];AVFormatContext *pFmtCtx = NULL;AVFormatContext *oFmtCtx = NULL;const AVOutputFormat *outFmt = NULL;AVPacket pkt;// 2 打开多媒体文件if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))< 0){av_log(NULL, AV_LOG_ERROR, "%d\n", ret);exit(-1);}// 4 打开目的文件的上下文件avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);if (!oFmtCtx){av_log(NULL, AV_LOG_ERROR, "no memory!");goto _ERROR;}stream_map = av_calloc(pFmtCtx->nb_streams, sizeof(int));if (!stream_map){av_log(NULL, AV_LOG_ERROR, "no memory!");goto _ERROR;}for ( i = 0; i < pFmtCtx->nb_streams; i++){AVStream *outStream = NULL;AVStream *inStream = pFmtCtx->streams[i];AVCodecParameters *inCodecPar = inStream->codecpar;if ((inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO)&&(inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO)&&(inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE)){stream_map[i] = -1;continue;}stream_map[i] = stream_index++;// 5 为目的文件,创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL); if (!outStream){av_log(oFmtCtx , AV_LOG_ERROR, "no memory!");goto _ERROR;} avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);outStream->codecpar->codec_tag = 0;}// 绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 7 写媒体文件头到目的文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 8 从源多媒体文件中读取音频/视频/字幕数据到目的文件中while (av_read_frame(pFmtCtx, &pkt) >= 0){AVStream *inStream = NULL;AVStream *outStream = NULL;inStream = pFmtCtx->streams[pkt.stream_index];if (stream_map[pkt.stream_index] < 0){av_packet_unref(&pkt);continue;}pkt.stream_index = stream_map[pkt.stream_index];outStream = oFmtCtx->streams[pkt.stream_index];av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base);pkt.pos = -1;av_interleaved_write_frame(oFmtCtx, &pkt);av_packet_unref(&pkt); }// 9 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10 将申请的资源释放掉_ERROR:if (pFmtCtx){avformat_close_input(&pFmtCtx);;pFmtCtx = NULL;}if (oFmtCtx->pb){avio_close(oFmtCtx->pb);}if (oFmtCtx){avformat_free_context(oFmtCtx);;oFmtCtx = NULL;}if (stream_map){av_free(stream_map);stream_map = NULL;}av_log(NULL, AV_LOG_INFO, "Hello wrold!\n");return 0;
}
9 视频裁剪
#include <stdio.h>
#include <stdlib.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = 0;int index = 0;int stream_index = 0;int i = 0;// 1 处理一些参数char* src;char* dst;double starttime = 0;double endtime = 0;int *stream_map = NULL;int64_t *dts_start_time = NULL;int64_t *pts_start_time = NULL;av_log_set_level(AV_LOG_DEBUG);// cut src dst start endif (argc < 5){av_log(NULL, AV_LOG_ERROR, "arguments must be more than 5 \n");exit(-1);}src = argv[1];dst = argv[2];starttime = atof(argv[3]);endtime = atof(argv[4]);AVFormatContext *pFmtCtx = NULL;AVFormatContext *oFmtCtx = NULL;const AVOutputFormat *outFmt = NULL;AVPacket pkt;// 2 打开多媒体文件if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))< 0){av_log(NULL, AV_LOG_ERROR, "%d\n", ret);exit(-1);}// 4 打开目的文件的上下文件avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);if (!oFmtCtx){av_log(NULL, AV_LOG_ERROR, "no memory!");goto _ERROR;}stream_map = av_calloc(pFmtCtx->nb_streams, sizeof(int));if (!stream_map){av_log(NULL, AV_LOG_ERROR, "no memory!");goto _ERROR;}for ( i = 0; i < pFmtCtx->nb_streams; i++){AVStream *outStream = NULL;AVStream *inStream = pFmtCtx->streams[i];AVCodecParameters *inCodecPar = inStream->codecpar;if ((inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO)&&(inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO)&&(inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE)){stream_map[i] = -1;continue;}stream_map[i] = stream_index++;// 5 为目的文件,创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL); if (!outStream){av_log(oFmtCtx , AV_LOG_ERROR, "no memory!");goto _ERROR;} avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);outStream->codecpar->codec_tag = 0;}// 绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// 7 写媒体文件头到目的文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}// seekret = av_seek_frame(pFmtCtx, -1, starttime * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);if (ret < 0){av_log(oFmtCtx, AV_LOG_ERROR, "%s ", av_err2str(ret));goto _ERROR;}dts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));for (int t = 0; t < pFmtCtx->nb_streams; t++){dts_start_time[t] = -1;}pts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));for (int t = 0; t < pFmtCtx->nb_streams; t++){pts_start_time[t] = -1;}// 8 从源多媒体文件中读取音频/视频/字幕数据到目的文件中while (av_read_frame(pFmtCtx, &pkt) >= 0){AVStream *inStream = NULL;AVStream *outStream = NULL;if ((dts_start_time[pkt.stream_index] == -1)&&(pkt.dts > 0)){dts_start_time[pkt.stream_index] = pkt.dts;}if ((pts_start_time[pkt.stream_index] == -1)&&(pkt.pts > 0)){pts_start_time[pkt.stream_index] = pkt.pts;}inStream = pFmtCtx->streams[pkt.stream_index];if (av_q2d(inStream->time_base) * pkt.pts > endtime){av_log(oFmtCtx, AV_LOG_INFO, "success!");break;}if (stream_map[pkt.stream_index] < 0){av_packet_unref(&pkt);continue;}pkt.pts = pkt.pts - pts_start_time[pkt.stream_index];pkt.dts = pkt.dts - dts_start_time[pkt.stream_index];if (pkt.dts > pkt.pts){pkt.pts = pkt.dts;}pkt.stream_index = stream_map[pkt.stream_index];outStream = oFmtCtx->streams[pkt.stream_index];av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base);pkt.pos = -1;av_interleaved_write_frame(oFmtCtx, &pkt);av_packet_unref(&pkt); }// 9 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10 将申请的资源释放掉_ERROR:if (pFmtCtx){avformat_close_input(&pFmtCtx);;pFmtCtx = NULL;}if (oFmtCtx->pb){avio_close(oFmtCtx->pb);}if (oFmtCtx){avformat_free_context(oFmtCtx);;oFmtCtx = NULL;}if (stream_map){av_free(stream_map);stream_map = NULL;}if (dts_start_time){av_free(dts_start_time);dts_start_time = NULL;}if (pts_start_time){av_free(pts_start_time);pts_start_time = NULL;}av_log(NULL, AV_LOG_INFO, "Hello wrold!\n");return 0;
}