Android 音视频ffmpeg

Posted 那年我七岁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 音视频ffmpeg 相关的知识,希望对你有一定的参考价值。

整个音视频流程

 

第一节,先做准备工作,功能有:1.前端展示; 2.解封装;3.各种错误回调

为什么要解封装,因为解完封装我们才能拿倒音视频信息,音视频流;

拿到音视频流我们才分开去解数据,才能绘制视频;

解封装的步骤及用的的函数:

第一步:打开媒体地址(文件路径, 直播地址rtmp)

avformat_alloc_context

avformat_open_input 

第二步:查找媒体中的音视频流的信息

avformat_find_stream_info

第三步:根据流信息,流的个数,用循环来找

第四步:获取媒体流(视频,音频)

formatContext->streams[stream_index];

第五步:从上面的流中 获取 编码解码的【参数】

AVCodecParameters *parameters = stream->codecpar;

第六步:(根据上面的【参数】)获取编解码器

avcodec_find_decoder(parameters->codec_id);

第七步:编解码器 上下文

avcodec_alloc_context3(codec);

第八步:他目前是一张白纸(parameters copy codecContext)

avcodec_parameters_to_context(codecContext, parameters);

第九步:打开解码器

avcodec_open2(codecContext, codec, nullptr)

第十步:从编解码器参数中,获取流的类型 codec_type  ===  音频 视频

parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO

第十一步: 如果流中 没有音频 也没有 视频 【健壮性校验】

第十二步:恭喜你,通知给上层;准备播放

 native-lib.cpp 代码:

JavaVM *vm = 0;
ANativeWindow *window = 0;

Player *player = nullptr;

jint JNI_OnLoad(JavaVM * vm, void * args) {
    ::vm = vm;
    return JNI_VERSION_1_6;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_game_demondk_DerryPlayer_prepareNative(JNIEnv *env, jobject job, jstring data_source) {
    const char * source = env->GetStringUTFChars(data_source,0);
    auto *hepler = new JINCallbackHelper(vm,env,job);
    player = new Player(env, source, hepler);
    player->perpare();
}

 Player.cpp代码:

Player::Player(JNIEnv *env, const char *data_source, JINCallbackHelper *pHelper) {
    this->env = env;
    this->data_source = new char[strlen(data_source) + 1];
    this->helper = pHelper;
    strcpy(this->data_source, data_source);
}


void *task_perpare(void *args) {
    auto *player = static_cast<Player *>(args);
    player->perpare_();
    return nullptr;
}

void Player::perpare() {
    pthread_create(thread_t, 0, task_perpare, this);
}

void Player::perpare_() {
    /**
     * TODO 第一步:打开媒体地址(文件路径, 直播地址rtmp)
     */
    avFormatContext = avformat_alloc_context();
    if (!avFormatContext) {
        return;
    }

    //添加参数
    AVDictionary *dictionary = nullptr;
    av_dict_set(&dictionary, "timeout", "5000000", 0);

    /**
     * 1,AVFormatContext *
     * 2,路径 url:文件路径或直播地址
     * 3,AVInputFormat *fmt  Mac、Windows 摄像头、麦克风, 我们目前安卓用不到
     * 4,各种设置:例如:Http 连接超时, 打开rtmp的超时  AVDictionary **options
     */
    int result = avformat_open_input(&avFormatContext, this->data_source, nullptr, &dictionary);
    // 释放字典
    av_dict_free(&dictionary);
    if (result) {
        if (helper) {
            helper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_OPEN_URL);
        }
        //关闭打开流
        avformat_close_input(&avFormatContext);
        return;
    }

    /**
     * TODO 第二步:查找媒体中的音视频流的信息
     */
    result = avformat_find_stream_info(avFormatContext, nullptr);
    if (result < 0) {
        if (helper) {
            helper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_FIND_STREAMS);
        }
        avformat_close_input(&avFormatContext);
        return;
    }

    //得到的总时长,要通过转换
    this->duration = avFormatContext->duration / AV_TIME_BASE;

    AVCodecContext *avCodecContext = nullptr;

    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        /**
         * TODO 第四步:获取媒体流(视频,音频)
         */
        AVStream *avStream = avFormatContext->streams[i];

        /**
         * TODO 第五步:从上面的流中 获取 编码解码的【参数】
         * 由于:后面的编码器 解码器 都需要参数(宽高 等等)
         */
        AVCodecParameters *parameters = avStream->codecpar;

        /**
         * TODO 第六步:(根据上面的【参数】)获取编解码器
         */
        AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
        if (!codec) {
            if (helper) {
                helper->onError(THREAD_CHILD, FFMPEG_FIND_DECODER_FAIL);
            }
            avformat_close_input(&avFormatContext);
        }

        /**
        * TODO 第七步:编解码器 上下文 (这个才是真正干活的)
        */
        avCodecContext = avcodec_alloc_context3(codec);
        if (!avCodecContext) {
            if (helper) {
                helper->onError(THREAD_CHILD, FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            }
            avcodec_free_context(&avCodecContext);
            avformat_close_input(&avFormatContext);
        }

        /**
         * TODO 第八步:他目前是一张白纸(parameters copy codecContext)
         */
        result = avcodec_parameters_to_context(avCodecContext, parameters);
        if (result < 0) {
            if (helper) {
                helper->onError(THREAD_CHILD, FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            }
            avcodec_free_context(&avCodecContext);
            avformat_close_input(&avFormatContext);
            return;
        }

        /**
         * TODO 第九步:打开解码器
         */
        result = avcodec_open2(avCodecContext, codec, nullptr);
        if (result) {
            if (helper) {
                helper->onError(THREAD_CHILD, FFMPEG_OPEN_DECODER_FAIL);
            }
            avcodec_free_context(&avCodecContext);
            avformat_close_input(&avFormatContext);
            return;
        }
        //音视频同步需要参数
        AVRational time_base = avStream->time_base;

        /**
         * TODO 第十步:从编解码器参数中,获取流的类型 codec_type  ===  音频 视频
         */
        if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {

        } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO) {

        }
    }
    /**
     * TODO 第十一步: 如果流中 没有音频 也没有 视频 【健壮性校验】
     */
    if (!audioChannel && !videoChannel) {
        if (helper) {
            helper->onError(THREAD_CHILD, FFMPEG_NOMEDIA);
        }
        if (avCodecContext) {
            //释放avCodecContext
            avcodec_free_context(&avCodecContext);
        }
        avformat_close_input(&avFormatContext);
    }

    /**
     * TODO 第十二步:准备成功,通知给上层
     */
    if (helper) { // 只要用户关闭了,就不准你回调给Java成 start播放
        helper->onPrepared(THREAD_CHILD);
    }
}
static {
    System.loadLibrary("native-lib");
}

加载native-lib的时候就会执行下面函数,javaVM 主要用来处理子线程回调,因为子线程env不可以垮线程执行,所以需要javaVM里的线程处理

jint JNI_OnLoad(JavaVM * vm, void * args) {
    ::vm = vm;
    return JNI_VERSION_1_6;
}

处理回调方法:

vm->AttachCurrentThread(&env_child, 0);
env_child->CallVoidMethod(job, jmd_prepared); 
vm->DetachCurrentThread();

以上是关于Android 音视频ffmpeg 的主要内容,如果未能解决你的问题,请参考以下文章

FFmpeg实现音视频同步的精准片段拼接

FFmpeg实现音视频同步的精准片段拼接

FFmpeg实现音视频同步的精准片段拼接

如何利用ffmpeg将一小段视频截取成图片

如何用ffmpeg截取视频片段&截取时间不准确的坑

TSINGSEE青犀视频通道FFmpeg获取视频片段的方法介绍