项目代码: https://blog.youkuaiyun.com/al4fun/article/details/104293868
下面代码展示了如何从mp4文件中提取aac音频流。而提取视频流的操作方法与提取音频流基本一致,详情可以参考项目代码。
其中一个主要的知识点是时间基的转换(时间基的相关概念可以参考这篇博文https://blog.youkuaiyun.com/bixinwei22/article/details/78770090,讲的非常通俗易懂)。
另外,使用FFmpeg提供的avformat_write_header
和av_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);
}
}