FFmpeg使用总结

1. FFmpeg常用命令

1.1 整体流程

1.2 常用命令分类

在这里插入图片描述

1.2.1 基本信息查询命令

在这里插入图片描述

1.2.2 录制命令

  1. 录制视频
ffmpeg -f avfoundation -r 30 -i 0 out.yuv
  1. 录制音频
ffmpeg -f avfoundation -i :0 out.pcm
  1. 音视频同时录制
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 处理原始数据命令

  1. 抽取视频
ffmpeg -i test.mp4 -an -c:v rawvideo test.yuv
  1. 抽取音频
ffmpeg -i test.mp4 -vn -ar 44100 -ac 2 -f s16le test.pcm

1.2.5 裁剪/合并命令

  1. 裁剪命令
ffmpeg -i test.mp4 -ss 00:00:01 -t 10 test_ss1.mp4
  1. 合并命令
#input.txt文件
file 'test_ss1.mp4'
file 'test_ss2.mp4'
#合并命令
ffmpeg -f concat -i input.txt test_cc.mp4

1.2.6 视频/图片转换命令

  1. 视频转图片
ffmpeg -i test.mp4 -f image2 image_%3d.jpeg
  1. 图片转视频
ffmpeg -i image_%3d.jpeg test_img.mp4

1.2.7 直播相关命令

  1. 拉流
ffmpeg -i rtmp://58.200.131.2:1935/livetv/cctv1 -c copy test_rtmp.mp4
  1. 拉流
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 抽取音频

  1. 相关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);
  1. 实例
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 格式转换

  1. 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();
  1. 实例
/*将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. 像素格式有问题

  1. 颜色偏红/绿/蓝,应该是RGB分量排序问题,RGB格式不对
    在这里插入图片描述
  2. 画面乱闪完全看不清楚,应该是像素格式问题,是不是YUV错选成RGB了
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值