FFmpeg提取音视频流

项目代码: https://blog.csdn.net/al4fun/article/details/104293868

下面代码展示了如何从mp4文件中提取aac音频流。而提取视频流的操作方法与提取音频流基本一致,详情可以参考项目代码。

其中一个主要的知识点是时间基的转换(时间基的相关概念可以参考这篇博文https://blog.csdn.net/bixinwei22/article/details/78770090,讲的非常通俗易懂)。

另外,使用FFmpeg提供的avformat_write_headerav_write_trailer可以自动生成并写入文件头尾信息,避免了自己手工拼接的麻烦。

//env: Android JNI, C++11, FFmpeg4.0.

extern "C"
JNIEXPORT void JNICALL
Java_com_example_helloffmpeg_MainActivity_extractAudio(JNIEnv *env, jobject thiz,
                                                       jstring src_path, jstring dst_path) {
    int ret;
    AVFormatContext *in_fmt_ctx = nullptr;
    int audio_index;
    AVStream *in_stream = nullptr;
    AVCodecParameters *in_codecpar = nullptr;
    AVFormatContext *out_fmt_ctx = nullptr;
    AVOutputFormat *out_fmt = nullptr;
    AVStream *out_stream = nullptr;
    AVPacket pkt;

    const char *srcPath = env->GetStringUTFChars(src_path, nullptr);
    const char *dstPath = env->GetStringUTFChars(dst_path, nullptr);
    __android_log_write(ANDROID_LOG_ERROR, TAG, srcPath);
    __android_log_write(ANDROID_LOG_ERROR, TAG, dstPath);

    //in_fmt_ctx
    ret = avformat_open_input(&in_fmt_ctx, srcPath, nullptr, nullptr);
    if (ret < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "avformat_open_input失败:%s",
                            av_err2str(ret));
        goto end;
    }

    //audio_index
    audio_index = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (audio_index < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "查找音频流失败:%s",
                            av_err2str(audio_index));
        goto end;
    }

    //in_stream、in_codecpar
    in_stream = in_fmt_ctx->streams[audio_index];
    in_codecpar = in_stream->codecpar;
    if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "The Codec type is invalid!");
        goto end;
    }

    //out_fmt_ctx
    out_fmt_ctx = avformat_alloc_context();
    out_fmt = av_guess_format(NULL, dstPath, NULL);
    out_fmt_ctx->oformat = out_fmt;
    if (!out_fmt) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "Cloud not guess file format");
        goto end;
    }

    //out_stream
    out_stream = avformat_new_stream(out_fmt_ctx, NULL);
    if (!out_stream) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to create out stream");
        goto end;
    }

    //拷贝编解码器参数
    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "avcodec_parameters_copy:%s",
                            av_err2str(ret));
        goto end;
    }
    out_stream->codecpar->codec_tag = 0;


    //创建并初始化目标文件的AVIOContext
    if ((ret = avio_open(&out_fmt_ctx->pb, dstPath, AVIO_FLAG_WRITE)) < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "avio_open:%s",
                            av_err2str(ret));
        goto end;
    }

    //initialize packet
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;

    //写文件头
    if ((ret = avformat_write_header(out_fmt_ctx, nullptr)) < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "avformat_write_header:%s",
                            av_err2str(ret));
        goto end;
    }

    while (av_read_frame(in_fmt_ctx, &pkt) == 0) {
        if (pkt.stream_index == audio_index) {
            //输入流和输出流的时间基可能不同,因此要根据时间基的不同对时间戳pts进行转换
            pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
            pkt.dts = pkt.pts;
            //根据时间基转换duration
            pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
            pkt.stream_index = 0;

            //写入
            av_interleaved_write_frame(out_fmt_ctx, &pkt);

            //释放packet
            av_packet_unref(&pkt);
        }
    }

    //写文件尾
    av_write_trailer(out_fmt_ctx);

    //释放资源
    end:
    env->ReleaseStringUTFChars(src_path, srcPath);
    env->ReleaseStringUTFChars(dst_path, dstPath);
    if (in_fmt_ctx) avformat_close_input(&in_fmt_ctx);
    if (out_fmt_ctx) {
        if (out_fmt_ctx->pb) avio_close(out_fmt_ctx->pb);
        avformat_free_context(out_fmt_ctx);
    }
}
发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/104314793