首页 > 其他分享 >FFmpeg:音视频封装(含格式转化、重采样)(参考muxing.c)

FFmpeg:音视频封装(含格式转化、重采样)(参考muxing.c)

时间:2023-01-14 19:44:46浏览次数:51  
标签:IOException FFmpeg frame ost ret 音视频 muxing new avcodec

如果不是特别熟悉C/C++,又要使用FFmpeg.API处理一些简单的音视频业务,那么可以使用org.bytedeco:ffmpeg-platform,下面记录一下使用ffmpeg-platform获取封装音视频数据(含格式转化、重采样)的方法。

1. 基本流程

音视频封装(复用)的基本流程主要有如下步骤:

  1. 准备音频流、视频流
  2. 构建输出流AVFormatContext
  3. 将音视频流AVStream添加到AVFormatContext
  4. 使用avio_open打开输出
  5. avformat_write_header写入头信息
  6. 写入音视频帧
  7. av_write_trailer写如尾信息

2. 输出流结构

这里将输出流(音频流、视频流)封装成一个内部类,结构如下:

class OutputStream {
    AVStream st;
    AVCodecContext enc;

    /* pts of the next frame that will be generated */
    long next_pts;
    int samples_count;

    AVFrame frame;
    AVFrame tmp_frame;

    AVPacket tmp_pkt;

    float t, tincr, tincr2;

    SwsContext sws_ctx;
    SwrContext swr_ctx;

    BytePointer y;
    BytePointer u;
    BytePointer v;
    BytePointer a;
}

3. 构建AVFormatContext

使用avformat_alloc_output_context2构建输出AVFormatContext对象:

int ret = avformat.avformat_alloc_output_context2(oc, null, null, output);
if (ret < 0) {
    ret = avformat.avformat_alloc_output_context2(oc, null, "mpeg", output);
}
if (ret < 0) {
    throw new IOException(ret + ":avformat_alloc_output_context2 error");
}

4. 添加输出流

音频、视频采用同一个函数,根据编码器区分,设置不同的参数:

private static AVCodec add_stream(OutputStream ost, AVFormatContext oc, int codec_id) throws IOException {
    AVCodec codec = avcodec.avcodec_find_encoder(codec_id);
    if (Objects.isNull(codec)) {
        throw new IOException("avcodec_find_encoder error");
    }

    ost.tmp_pkt = avcodec.av_packet_alloc();
    if (Objects.isNull(ost.tmp_pkt)) {
        throw new IOException("av_packet_alloc error");
    }

    ost.st = avformat.avformat_new_stream(oc, null);
    if (Objects.isNull(ost.st)) {
        throw new IOException("avformat_new_stream error");
    }

    ost.st.id(oc.nb_streams() - 1);
    AVCodecContext c = avcodec.avcodec_alloc_context3(codec);
    if (Objects.isNull(c)) {
        throw new IOException("avcodec_alloc_context3");
    }
    ost.enc = c;
    switch (codec.type()) {
        case avutil.AVMEDIA_TYPE_AUDIO:
            c.sample_fmt(
                    Objects.nonNull(codec.sample_fmts()) ? codec.sample_fmts().get() : avutil.AV_SAMPLE_FMT_FLTP);
            c.bit_rate(64000);
            c.sample_rate(44100);
            if (Objects.nonNull(codec.supported_samplerates())) {
                c.sample_rate(codec.supported_samplerates().get());
                for (int i = 0; codec.supported_samplerates().get(i) != 0; i++) {
                    if (codec.supported_samplerates().get(i) == 44100) {
                        c.sample_rate(44100);
                    }
                }
            }
            // @see libavutil/channel_layout.h
            // #define AV_CHANNEL_LAYOUT_MASK(nb, m) \
            //    { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = (nb), .u = { .mask = (m) }}
            // #define AV_CHANNEL_LAYOUT_STEREO            AV_CHANNEL_LAYOUT_MASK(2,  AV_CH_LAYOUT_STEREO)
            // avutil.av_channel_layout_copy(c.ch_layout(), )
            c.ch_layout().nb_channels(2);
            c.ch_layout().order(avutil.AV_CHANNEL_ORDER_NATIVE);
            c.ch_layout().u_mask(avutil.AV_CH_LAYOUT_STEREO);
            AVRational ar = new AVRational();
            ar.num(1);
            ar.den(c.sample_rate());
            ost.st.time_base(ar);
            break;
        case avutil.AVMEDIA_TYPE_VIDEO:
            c.codec_id(codec_id);
            c.bit_rate(400000);
            /* Resolution must be a multiple of two. */
            c.width(352);
            c.height(288);
            /*
             * timebase: This is the fundamental unit of time (in seconds) in terms of which
             * frame timestamps are represented. For fixed-fps content, timebase should be
             * 1/framerate and timestamp increments should be identical to 1.
             */
            AVRational vr = new AVRational();
            vr.num(1);
            vr.den(STREAM_FRAME_RATE);
            ost.st.time_base(vr);
            c.time_base(ost.st.time_base());
            /* emit one intra frame every twelve frames at most */
            c.gop_size(12);
            c.pix_fmt(STREAM_PIX_FMT);
            if (c.codec_id() == avcodec.AV_CODEC_ID_MPEG2VIDEO) {
                /* just for testing, we also add B-frames */
                c.max_b_frames(2);
            }
            if (c.codec_id() == avcodec.AV_CODEC_ID_MPEG1VIDEO) {
                /*
                 * Needed to avoid using macroblocks in which some coeffs overflow. This does
                 * not happen with normal video, it just happens here as the motion of the
                 * chroma plane does not match the luma plane.
                 */
                c.mb_decision(2);
            }
            break;
    }
    if ((oc.oformat().flags() & avformat.AVFMT_GLOBALHEADER) != 0) {
        c.flags(c.flags() | avcodec.AV_CODEC_FLAG_GLOBAL_HEADER);
    }

    return codec;
}

5. 设置参数

设置视频流参数:

private static void open_video(AVCodec codec, OutputStream ost) throws IOException {
    AVCodecContext c = ost.enc;
    // open the codec
    int ret = avcodec.avcodec_open2(c, codec, (AVDictionary) null);
    if (ret < 0) {
        throw new IOException(ret + ":avcodec_open2 error");
    }

    ost.frame = alloc_picture(c.pix_fmt(), c.width(), c.height());
    if (Objects.isNull(ost.frame)) {
        throw new IOException("alloc_picture error");
    }

    /* allocate and init a re-usable frame */
    if (c.pix_fmt() != avutil.AV_PIX_FMT_YUV420P) {
        ost.tmp_frame = alloc_picture(avutil.AV_PIX_FMT_YUV420P, c.width(), c.height());
        if (Objects.isNull(ost.tmp_frame)) {
            throw new IOException("alloc_picture error");
        }
    }

    // copy the stream parameters to the muxer
    ret = avcodec.avcodec_parameters_from_context(ost.st.codecpar(), c);
    if (ret < 0) {
        throw new IOException(ret + ":avcodec_parameters_from_context error");
    }
}

设置音频流参数:

private static void open_audio(AVCodec codec, OutputStream ost) throws IOException {
    AVCodecContext c = ost.enc;
    int ret = avcodec.avcodec_open2(c, codec, (AVDictionary) null);
    if (ret < 0) {
        throw new IOException(ret + ":avcodec_open2 error");
    }

    // init signal generator
    ost.t = 0;
    ost.tincr = (float) (2 * Math.PI * 100.0 / c.sample_rate());
    ost.tincr2 = (float) (2 * Math.PI * 100.0 / c.sample_rate() / c.sample_rate());

    int nb_samples;
    if ((c.codec().capabilities() & avcodec.AV_CODEC_CAP_VARIABLE_FRAME_SIZE) != 0) {
        nb_samples = 10000;
    } else {
        nb_samples = c.frame_size();
    }

    ost.frame = alloc_audio_frame(c.sample_fmt(), c.ch_layout(), c.sample_rate(), nb_samples);
    ost.tmp_frame = alloc_audio_frame(avutil.AV_SAMPLE_FMT_S16, c.ch_layout(), c.sample_rate(), nb_samples);

    // copy the stream parameters to the muxer
    ret = avcodec.avcodec_parameters_from_context(ost.st.codecpar(), c);
    if (ret < 0) {
        throw new IOException(ret + "avcodec_parameters_from_context error");
    }

    ost.swr_ctx = swresample.swr_alloc();
    if (Objects.isNull(ost.swr_ctx)) {
        throw new IOException("swr_alloc error");
    }

    // set options
    avutil.av_opt_set_chlayout(ost.swr_ctx, "in_chlayout", c.ch_layout(), 0);
    avutil.av_opt_set_int(ost.swr_ctx, "in_sample_rate", c.sample_rate(), 0);
    avutil.av_opt_set_sample_fmt(ost.swr_ctx, "in_sample_fmt", avutil.AV_SAMPLE_FMT_S16, 0);
    avutil.av_opt_set_chlayout(ost.swr_ctx, "out_chlayout", c.ch_layout(), 0);
    avutil.av_opt_set_int(ost.swr_ctx, "out_sample_rate", c.sample_rate(), 0);
    avutil.av_opt_set_sample_fmt(ost.swr_ctx, "out_sample_fmt", c.sample_fmt(), 0);

    ret = swresample.swr_init(ost.swr_ctx);
    if (ret < 0) {
        throw new IOException(ret + ":swr_init error");
    }
}

6. 打开输出

设置FLAG为WRITTE:

// open the output file, if needed
if ((fmt.flags() & avformat.AVFMT_NOFILE) == 0) {
    AVIOContext pb = new AVIOContext(null);
    ret = avformat.avio_open(pb, output, avformat.AVIO_FLAG_WRITE);
    if (ret < 0) {
        throw new IOException(ret + ":avio_open error");
    }
    oc.pb(pb);
}

7. 写入流数据

写入头信息:

// Write the stream header, if any
ret = avformat.avformat_write_header(oc, (AVDictionary) null);
if (ret < 0) {
    // av_err2str(ret)
    throw new IOException(ret + ":avformat_write_header error");
}

写入音频、视频数据:

while (encode_video || encode_audio) {
    if (encode_video && (!encode_audio || avutil.av_compare_ts(video_st.next_pts, video_st.enc.time_base(),
            audio_st.next_pts, audio_st.enc.time_base()) <= 0)) {
        System.out.println(">>> encode_video >>>");
        encode_video = write_video_frame(oc, video_st) == 0;
    } else {
        System.out.println("<<< encode_audio <<<");
        encode_audio = write_audio_frame(oc, audio_st) == 0;
    }
}

下面是具体的写入方法:

// encode one video frame and send it to the muxer return 1 when encoding is finished, 0 otherwise
private static int write_video_frame(AVFormatContext oc, OutputStream ost) throws IOException {
    return write_frame(oc, ost.enc, ost.st, get_video_frame(ost), ost.tmp_pkt);
}

// encode one audio frame and send it to the muxer return 1 when encoding is finished, 0 otherwise
private static int write_audio_frame(AVFormatContext oc, OutputStream ost) throws IOException {
    AVFrame frame = get_audio_frame(ost);
    AVCodecContext c = ost.enc;
    if (Objects.nonNull(frame)) {
        // convert samples from native format to destination codec format, using the resampler
        // compute destination number of samples
        long dst_nb_samples = avutil.av_rescale_rnd(
                swresample.swr_get_delay(ost.swr_ctx, c.sample_rate()) + frame.nb_samples(), c.sample_rate(),
                c.sample_rate(), AV_ROUND_UP);

        // when we pass a frame to the encoder, it may keep a reference to it internally;
        // make sure we do not overwrite it here
        int ret = avutil.av_frame_make_writable(ost.frame);
        if (ret < 0) {
            throw new IOException(ret + ":av_frame_make_writable error");
        }

        // convert to destination format
        ret = swresample.swr_convert(ost.swr_ctx, ost.frame.data(), (int) dst_nb_samples, frame.data(),
                frame.nb_samples());
        if (ret < 0) {
            throw new IOException(ret + ":swr_convert error");
        }

        frame = ost.frame;

        AVRational rational = new AVRational();
        rational.num(1);
        rational.den(c.sample_rate());
        frame.pts(avutil.av_rescale_q(ost.samples_count, rational, c.time_base()));
        ost.samples_count += dst_nb_samples;
    }
    return write_frame(oc, c, ost.st, frame, ost.tmp_pkt);
}

private static int write_frame(AVFormatContext fmt_ctx, AVCodecContext c, AVStream st, AVFrame frame, AVPacket pkt)
        throws IOException {
    // System.out.println("send st: " + st.id() + "," + frame);
    int ret = avcodec.avcodec_send_frame(c, frame);
    if (ret < 0) {
        throw new IOException(ret + ":avcodec_send_frame error");
    }

    while (true) {
        ret = avcodec.avcodec_receive_packet(c, pkt);
        if (ret == avutil.AVERROR_EAGAIN() || ret == avutil.AVERROR_EOF()) {
            break;
        } else if (ret < 0) {
            throw new IOException(ret + ":avcodec_receive_packet error");
        }

        // rescale output packet timestamp values from codec to stream timebase
        avcodec.av_packet_rescale_ts(pkt, c.time_base(), st.time_base());
        pkt.stream_index(st.index());

        // Write the compressed frame to the media file.
        ret = avformat.av_interleaved_write_frame(fmt_ctx, pkt);
        if (ret < 0) {
            throw new IOException(ret + ":av_interleaved_write_frame error");
        }
    }

    return ret == avutil.AVERROR_EOF() ? 1 : 0;
}

写入的音视频数据由程序生成:

private static AVFrame get_video_frame(OutputStream ost) throws IOException {
    AVRational rational = new AVRational();
    rational.num(1);
    rational.den(1);
    if (avutil.av_compare_ts(ost.next_pts, ost.enc.time_base(), STREAM_DURATION, rational) > 0) {
        return null;
    }
    int ret = avutil.av_frame_make_writable(ost.frame);
    if (ret < 0) {
        throw new IOException(ret + ":av_frame_make_writable error");
    }

    AVCodecContext c = ost.enc;
    if (c.pix_fmt() != avutil.AV_PIX_FMT_YUV420P) {
        // as we only generate a YUV420P picture, we must convert it
        // to the codec pixel format if needed
        if (Objects.isNull(ost.sws_ctx)) {
            ost.sws_ctx = swscale.sws_getContext(c.width(), c.height(), avutil.AV_PIX_FMT_YUV420P, c.width(),
                    c.height(), c.pix_fmt(), SCALE_FLAGS, null, null, (DoublePointer) null);
            if (Objects.isNull(ost.sws_ctx)) {
                throw new IOException("sws_getContext error");
            }
        }
        fill_yuv_image(ost, ost.frame, (int) ost.next_pts, c.width(), c.height());
        swscale.sws_scale(ost.sws_ctx, ost.tmp_frame.data(), ost.tmp_frame.linesize(), 0, c.height(),
                ost.frame.data(), ost.frame.linesize());
    } else {
        fill_yuv_image(ost, ost.frame, (int) ost.next_pts, c.width(), c.height());
    }

    ost.frame.pts(ost.next_pts++);
    return ost.frame;
}

private static void fill_yuv_image(OutputStream ost, AVFrame pict, int frame_index, int width, int height) {
    int x, y, i;
    i = frame_index;

    if (Objects.isNull(ost.y)) {
        ost.y = new BytePointer(new byte[width * height]);
        ost.u = new BytePointer(new byte[width * height * 2 / 3]);
        ost.v = new BytePointer(new byte[width * height * 2 / 3]);

        pict.data(0, ost.y);
        pict.data(1, ost.u);
        pict.data(2, ost.v);
    }

    // Y
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            // yBuffer[y * pict.linesize(0) + x] = (byte) (x + y + i * 3);
            // System.out.println("1:" + (y * pict.linesize(0) + x) + ":" + pict.linesize(0));
            pict.data(0).put(y * pict.linesize(0) + x, (byte) (x + y + i * 3));
        }
    }
    // Cb and Cr
    for (y = 0; y < height / 2; y++) {
        for (x = 0; x < width / 2; x++) {
            // uBuffer[y * pict.linesize(1) + x] = (byte) (128 + y + i * 2);
            // System.out.println("1:" + (y * pict.linesize(1) + x) + ":" + pict.linesize(1));
            // System.out.println("2:" + (y * pict.linesize(2) + x) + ":" + pict.linesize(2));
            pict.data(1).put(y * pict.linesize(1) + x, (byte) (128 + y + i * 2));
            // vBuffer[y * pict.linesize(2) + x] = (byte) (64 + x + i * 5);
            pict.data(2).put(y * pict.linesize(2) + x, (byte) (64 + x + i * 5));
        }
    }
}

private static AVFrame get_audio_frame(OutputStream ost) {
    // check if we want to generate more frames
    AVRational rational = new AVRational();
    rational.num(1);
    rational.den(1);
    if (avutil.av_compare_ts(ost.next_pts, ost.enc.time_base(), STREAM_DURATION, rational) > 0) {
        return null;
    }

    AVFrame frame = ost.tmp_frame;
    if (Objects.isNull(ost.a)) {
        ost.a = new BytePointer(new byte[frame.nb_samples() * 2 * ost.enc.ch_layout().nb_channels()]);
        frame.data(0, ost.a);
    }

    int j, i, v, off = 0;
    for (j = 0; j < frame.nb_samples(); j++) {
        v = (int) (Math.sin(ost.t) * 10000);
        for (i = 0; i < ost.enc.ch_layout().nb_channels(); i++) {
            frame.data(0).put(off++, (byte) (v & 0xff));
            frame.data(0).put(off++, (byte) ((v >> 8) & 0xff));
        }
        ost.t += ost.tincr;
        ost.tincr += ost.tincr2;
    }
    frame.pts(ost.next_pts);
    ost.next_pts += frame.nb_samples();
    return frame;
}

写入尾信息:

avformat.av_write_trailer(oc);

8. 结果演示

最终生成的效果:

完整代码:扫描左侧头像小程序获取,或私信联系。

标签:IOException,FFmpeg,frame,ost,ret,音视频,muxing,new,avcodec
From: https://www.cnblogs.com/michong2022/p/17052414.html

相关文章

  • 使用ffmpeg合并多个ts文件
    使用ffmpeg合并多个ts文件需求来源在海康的行车记录仪上录制的视频为每分钟一个ts文件,在手机上最多也就可以设置为3分钟,并且在APP只能一下导出5个文件,所以我就将内存卡拔......
  • uniapp裁剪视频使用ffmpeg
    <template><view><button@click="cutVideo">CutVideo</button></view></template><script>importffmpegfrom'ffmpeg.js'exportdefault{method......
  • Qt音视频开发11-通用监控布局控件(开源)
    一、前言自从做监控系统以来,就一直有打算将这个功能独立出来一个类,这样的话很多系统用到此类布局切换,通用这个类就行,而且后期此布局会增加其他异形布局,甚至按照16:9之类的......
  • ffmpeg第8篇:使用ffprobe采集文件信息
    1、前言ffprobe是ffmpeg的其中一个模块,主要用于查看文件信息,咱们知道一个MP4文件其实不仅仅包含了音视频数据,还有如元数据等其它信息,但是实际上咱们关心的往往是音视频......
  • 音视频
    音视频历史大家都有在网页中浏览视频的经历,但在HTML5之前,对视频乃至音频都还没有一个标准因此在网页中看到的视频,都是通过第三插件的方式嵌入的,可能是QuickTime,也可能是R......
  • FFmpeg-最最强大的视频工具
    FFmpeg-最最强大的视频工具FFmpeg是一个自由软件,它可以用于处理音频和视频数据。它包含了许多命令行工具,可以进行各种不同的媒体处理任务,如音频编解码、视频编解码、转码、......
  • WEB端音视频设备获取及流处理
    获取设备所有设备asyncfunctiongetDevices(){letdevices=awaitnavigator.mediaDevices.enumerateDevices()console.info(devices);}getDevices()......
  • ffmpeg安装图片转视频
     乌班图:ffmpeg图片转视频一键安装命令:aptinstallffmpeg Centos1.在宝塔终端输入:wgethttp://download.bt.cn/install/ext/ffmpeg.sh&&shffmpeg.sh2.......
  • Window下安装ffmpeg
    下载ffmpeg    下载ffmpeg-git-full.7z   ......
  • Qt音视频开发10-ffmpeg内核硬解码
    一、前言为了极大的降低CPU的占用,实现硬解码(也叫硬件加速)非常有必要,一个视频文件或者一路视频流还好,如果增加到64路视频流呢,如果是4K、8K这种高分辨率的视频呢,必须安装上......