1. FFmpeg常用命令
1.1 整体流程
1.2 常用命令分类
1.2.1 基本信息查询命令
1.2.2 录制命令
- 录制视频
ffmpeg -f avfoundation -r 30 -i 0 out.yuv
- 录制音频
ffmpeg -f avfoundation -i :0 out.pcm
- 音视频同时录制
ffmpeg -f avfoundation -r 30 -i 0:0 out.mp4
1.2.3 分解/复用命令
ffmpeg -i test.mp4 -c copy test.flv
# 同样的效果
ffmpeg -i test.mp4 -acodec copy -vcodec copy test.flv
ffmpeg -i test.mp4 -c:a copy -c:v copy test.flv
1.2.4 处理原始数据命令
- 抽取视频
ffmpeg -i test.mp4 -an -c:v rawvideo test.yuv
- 抽取音频
ffmpeg -i test.mp4 -vn -ar 44100 -ac 2 -f s16le test.pcm
1.2.5 裁剪/合并命令
- 裁剪命令
ffmpeg -i test.mp4 -ss 00:00:01 -t 10 test_ss1.mp4
- 合并命令
#input.txt文件
file 'test_ss1.mp4'
file 'test_ss2.mp4'
#合并命令
ffmpeg -f concat -i input.txt test_cc.mp4
1.2.6 视频/图片转换命令
- 视频转图片
ffmpeg -i test.mp4 -f image2 image_%3d.jpeg
- 图片转视频
ffmpeg -i image_%3d.jpeg test_img.mp4
1.2.7 直播相关命令
- 拉流
ffmpeg -i rtmp://58.200.131.2:1935/livetv/cctv1 -c copy test_rtmp.mp4
- 拉流
ffmpeg -re -i test_rtmp.mp4 -c copy -f flv rtmp//:localhost/live/room
1.2.8 滤镜相关命令
裁剪画面
ffmpeg -i test.mp4 -vf crop=in_h-400:in_w-900 test_crop.mp4
2. FFmpeg初级使用
2.1 文件操作
void ffmpeg_test() {
av_log_set_level(AV_LOG_DEBUG);
AVIODirContext *dir_ctx = NULL;
AVIODirEntry *next = NULL;
//打开目录
avio_open_dir(&dir_ctx, "/Users/mac/AVTest", NULL);
//获取目录信息
int count = 0;
while (avio_read_dir(dir_ctx, &next)) {
//打印目录信息
if(!next) {
break;
}
av_log(NULL, AV_LOG_DEBUG, "%s %lld\n", next->name, next->size);
}
//删除文件
avpriv_io_delete("/Users/mac/AVTest/test.flv");
//重命名文件
avpriv_io_move("/Users/mac/AVTest/test.pcm", "/Users/mac/AVTest/tt.pcm");
}
2.2 抽取音频
- 相关API
//输出文件的流信息
void av_dump_format(AVFormatContext *ic,
int index,
const char *url,
int is_output);
//找到最合适的流数据
int av_find_best_stream(AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec **decoder_ret,
int flags);
- 实例
void cut_audio() {
AVFormatContext *fmt_ctx = NULL;
const char *src_path = "/Users/mac/AVTest/test.mp4";
const char *dst_path = "/Users/mac/AVTest/test.aac";
FILE *dst_fd = NULL;
AVPacket *packet NULL;
int ret = -1;
/*输出文件的信息*/
avdevice_register_all();
//打开音频文件
avformat_open_input(&fmt_ctx, src_path, NULL, NULL);
av_dump_format(fmt_ctx, 0, src_path, 0);
//选择最合适的流
ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
//读取数据流
dst_fd = fopen(dst_path, "wb+");
packet = av_packet_alloc();
av_init_packet(packet);
while (0 == av_read_frame(fmt_ctx, packet)) {
//转存入文件
char szAdtsHeader[ADTS_HEADER_LEN] = {0};
adts_header(szAdtsHeader, packet->size);
fwrite(szAdtsHeader, 1, ADTS_HEADER_LEN, dst_fd);
fwrite(packet->data, 1, packet->size, dst_fd);
}
fclose(dst_fd);
avformat_close_input(&fmt_ctx);
av_packet_free(&packet);
}
2.3抽取视频
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#ifndef AV_WB32
# define AV_WB32(p, val) do { \
uint32_t d = (val); \
((uint8_t*)(p))[3] = (d); \
((uint8_t*)(p))[2] = (d)>>8; \
((uint8_t*)(p))[1] = (d)>>16; \
((uint8_t*)(p))[0] = (d)>>24; \
} while(0)
#endif
#ifndef AV_RB16
# define AV_RB16(x) \
((((const uint8_t*)(x))[0] << 8) | \
((const uint8_t*)(x))[1])
#endif
//根据NALU_type不同添加不同的startcode
static int alloc_and_copy(AVPacket *out,
const uint8_t *sps_pps, uint32_t sps_pps_size,
const uint8_t *in, uint32_t in_size)
{
//判断startcode的字节数:若out中有数据,则说明当前不是第一帧,即非sps/pps/IDR帧;若size为0,则说明当前为第一帧,startcode为4byte
uint32_t offset = out->size;
uint8_t nal_header_size = offset ? 3 : 4;
int err;
//扩充out的空间:NALU_size + SPS_size + startcode_size
//alloc_and_copy(out, spspps_pkt->data, spspps_pkt->size, buf, nal_size);
err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size);
//若是sps/pps
if (sps_pps) {
//拷贝 sps/pps的data到out_data
memcpy(out->data + offset, sps_pps, sps_pps_size);
}
//拷贝 packet中的size追加到out中sps/pps数据及预留的startcode位置的后面
memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
//设置00000001
if (!offset) {
AV_WB32(out->data + sps_pps_size, 1);
} else { //设置000001
(out->data + offset + sps_pps_size)[0] =
(out->data + offset + sps_pps_size)[1] = 0;
(out->data + offset + sps_pps_size)[2] = 1;
}
return 0;
}
//从codec中获取sps/pps数据
//h264_extradata_to_annexb(fmt_ctx->stream[in->stream_index]->codec->extradata, in->stream_index]->codec->extradata——size, &spspps_pkt, AV_INPUT_BUFFER_PADDING_SIZE);
int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
uint16_t unit_size;
uint64_t total_size = 0;
uint8_t *out = NULL, unit_nb, sps_done = 0,
sps_seen = 0, pps_seen = 0, sps_offset = 0, pps_offset = 0;
//跳过无用的前4byte
const uint8_t *extradata = codec_extradata + 4;
static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
//获取第一个字节的后2bit:sps/pps所占用的空间
int length_size = (*extradata++ & 0x3) + 1;
sps_offset = pps_offset = -1;
//获取第二个字节的后5bit:sps/pps的个数,一般一个sps/pps
unit_nb = *extradata++ & 0x1f;
if (!unit_nb) {
goto pps;
}else {
sps_offset = 0;
sps_seen = 1;
}
//遍历每一个sps/pps
while (unit_nb--) {
int err;
//获取sps/pps的长度
unit_size = AV_RB16(extradata);
//添加startcode的长度
total_size += unit_size + 4;
//对空间进行扩展
if ((err = av_reallocp(&out, total_size + padding)) < 0)
return err;
//拷贝startcode
memcpy(out + total_size - unit_size - 4, nalu_header, 4);
//拷贝sps/pps数据
memcpy(out + total_size - unit_size, extradata + 2, unit_size);
extradata += 2 + unit_size;
pps:
if (!unit_nb && !sps_done++) {
unit_nb = *extradata++; /* number of pps unit(s) */
if (unit_nb) {
pps_offset = total_size;
pps_seen = 1;
}
}
}
if (out)
memset(out + total_size, 0, padding);
//返回获取的数据
out_extradata->data = out;
out_extradata->size = total_size;
return length_size;
}
int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd)
{
AVPacket *out = NULL;
AVPacket spspps_pkt;
int len;
uint8_t unit_type;
int32_t nal_size;
uint32_t cumul_size = 0;
const uint8_t *buf;
const uint8_t *buf_end;
int buf_size;
int ret = 0, i;
out = av_packet_alloc();
buf = in->data;
buf_size = in->size;
buf_end = in->data + in->size;
do {
//获取data的前4个byte并转为大端,代表NALU的大小
for (nal_size = 0, i = 0; i<4; i++)
nal_size = (nal_size << 8) | buf[i];
buf += 4; //跳过NALU_size
//第一个字节为NALU_header:后5bit代表NALU_type
unit_type = *buf & 0x1f;
//若当前帧是关键帧
if (unit_type == 5)
//获取sps/pps的数据
h264_extradata_to_annexb(fmt_ctx->stream[in->stream_index]->codec->extradata, &spspps_pkt, AV_INPUT_BUFFER_PADDING_SIZE);
//添加startcode
alloc_and_copy(out, spspps_pkt->data, spspps_pkt->size, buf, nal_size);
} else {
//直接添加startcode
alloc_and_copy(out, NULL, 0, buf, nal_size);
}
//将annexb格式的NALU写到输出文件
fwrite( out->data, 1, out->size, dst_fd);
fflush(dst_fd);
//获取当前从pakcet中读出了多少数据数据:NALU+NALU_size
buf += nal_size;
cumul_size += nal_size + 4;
//若没有读完,说明不只存了一帧
} while (cumul_size < buf_size);
return ret;
}
int main()
{
int video_stream_index = -1;
AVFormatContext *fmt_ctx = NULL;
AVPacket pkt;
/*register all formats and codec*/
av_register_all();
FILE *dst_fd = fopen(dst_filename, "test.mp4");
/*open input media file, and allocate format context*/
avformat_open_input(&fmt_ctx, src_filename, NULL, NULL);
/*dump input information*/
av_dump_format(fmt_ctx, 0, src_filename, 0);
/*initialize packet*/
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
/*find best video stream*/
video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
/*read frames from media file*/
while(av_read_frame(fmt_ctx, &pkt) >=0 ){
if(pkt.stream_index == video_stream_index){
h264_mp4toannexb(fmt_ctx, &pkt, dst_fd);
}
//release pkt->data
av_packet_unref(&pkt);
}
/*close input media file*/
avformat_close_input(&fmt_ctx);
if(dst_fd) {
fclose(dst_fd);
}
return 0;
}
2.4 格式转换
- API
//打开/释放输出文件
avformat_alloc_output_context2();
avformat_free_context();
//创建新的流数据
avformat_new_stream();
//复制流数据的编解码参数
avcodec_parameters_copy();
//写入多媒体文件头
avformat_write_header();
//写入流数据
av_write_frame();av_interleaved_write_fream();
//写入流数据尾部
av_write_trailer();
- 实例
/*将mp4转成flv格式*/
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
int main(int argc, char **argv)
{
AVOutputFormat *ofmt = NULL; // 输出格式
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL; // 输入、输出是上下文环境
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *stream_mapping = NULL; // 数组用于存放输出文件流的Index
int stream_mapping_size = 0; // 输入文件中流的总数量
if (argc < 3) {
printf("usage: %s input output\n"
"API example program to remux a media file with libavformat and libavcodec.\n"
"The output format is guessed according to the file extension.\n"
"\n", argv[0]);
return 1;
}
in_filename = argv[1];
out_filename = argv[2];
// 打开输入文件为ifmt_ctx分配内存
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
// 检索输入文件的流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
// 打印输入文件相关信息
av_dump_format(ifmt_ctx, 0, in_filename, 0);
// 为输出上下文环境分配内存
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 输入文件流的数量
stream_mapping_size = ifmt_ctx->nb_streams;
// 分配stream_mapping_size段内存,每段内存大小是sizeof(*stream_mapping)
stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
// 输出文件格式
ofmt = ofmt_ctx->oformat;
// 遍历输入文件中的每一路流,对于每一路流都要创建一个新的流进行输出
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream; // 输出流
AVStream *in_stream = ifmt_ctx->streams[i]; // 输入流
AVCodecParameters *in_codecpar = in_stream->codecpar; // 输入流的编解码参数
// 只保留音频、视频、字幕流,其他的流不需要
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}
// 对于输出的流的index重写编号
stream_mapping[i] = stream_index++;
// 创建一个对应的输出流
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 直接将输入流的编解码参数拷贝到输出流中
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
// 打印要输出的多媒体文件的详细信息
av_dump_format(ofmt_ctx, 0, out_filename, 1);
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
// 写入新的多媒体文件的头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
// 循环读取每一帧数据
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0) // 读取完后退出循环
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping[pkt.stream_index] < 0) {
av_packet_unref(&pkt);
continue;
}
pkt.stream_index = stream_mapping[pkt.stream_index]; // 按照输出流的index给pkt重新编号
out_stream = ofmt_ctx->streams[pkt.stream_index]; // 根据pkt的stream_index获取对应的输出流
// 对pts、dts、duration进行时间基转换,不同格式时间基都不一样,不转换会导致音视频同步问题
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
// 将处理好的pkt写入输出文件
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&pkt);
}
// 写入新的多媒体文件尾
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
av_freep(&stream_mapping);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
ffmpeg处理常见问题
1. 像素尺寸有问题
1.视频画面跳来跳去且画面渲染的不完整
2. 画面渲染的不完整
2. 像素格式有问题
- 颜色偏红/绿/蓝,应该是RGB分量排序问题,RGB格式不对
- 画面乱闪完全看不清楚,应该是像素格式问题,是不是YUV错选成RGB了