android ffmpeg播放本地视频

2020-04-18  本文已影响0人  小明叔叔_乐

一、思路:

Java层:初始化一个surfaceView,赋值给Jni层作为播放载体,设置播放路径,在ffmpeg初始化完成后,开始正式播放本地视频。
Jni层:通过Java层赋值的surface获取到window对象(真正显示播放的对象窗体),在获取到视频路径后,初始化ffmpeg,初始化成功后通过反射通知Java层,开始解码视频,把读包线程得到的packet统一放到packet queue中,解码线程不停地从packet queue中获取packet,并进行解码,解码得到的frame对象统一放到frame queue中,显示线程不停地从frame queue中获取frame对象,并显示在window对象中。

二、上代码:

Java层:
    private void initView() {
        // Java层 第一步: 拿到一个surfaceView,作为播放载体
        surfaceView = findViewById(R.id.surfaceView);
    }

    private void initPlayHelper() {
        // Java层 第二步: 把surfaceView设置给playerHelper类
        playerHelper = new PlayerHelper();
        playerHelper.setSurfaceView(surfaceView);
        // 设置相应的监听
        playerHelper.setOnPrepareListener(this);
        playerHelper.setOnProgressListener(this);
        playerHelper.setOnErrorListener(this);
    }

    /**
     * 开始播放画面
     *
     * @param view
     */
    public void startPlay(View view) {
        // Java层 第三步: 把播放路径设置给playerHelper类,(路径可以是本地路径,也可以是远端路径)
        File inputMp4 = new File(Environment.getExternalStorageDirectory(), "ffmpeg-test.mp4");    //opensldemo.mp3
        playerHelper.setDataSource(inputMp4.getAbsolutePath());
    }
Jni层:
FfmpegHelper.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_hele_zandroid_ffmpeg_PlayerHelper_native_1set_1surface(JNIEnv *env, jobject thiz,
                                                                jobject surface) {
    // Jni层 第一步:通过Java层传递的surface,创建一个全局的window对象
    // 创建window
    if (window) {
        ANativeWindow_release(window);
        window = 0;
    }
    LOGD("native set window");
    window = ANativeWindow_fromSurface(env, surface);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_hele_zandroid_ffmpeg_PlayerHelper_native_1prepare(JNIEnv *env, jobject instance,
                                                           jstring dataSource_) {
    const char *dataSource = env->GetStringUTFChars(dataSource_, 0);
    // Jni层 第二步:初始化ffmpeg
    // 定义一个c层回调java层的辅助对象,使c层可以主动调用java层的方法
    javaCallHelper = new JavaCallHelper(javaVM, env,
                                        instance);     // 这个instance就是PlayHelper.java对象

    // 注意框架,交给控制层来处理
    ffmpegHelper = new FfmpegHelper(javaCallHelper, dataSource);
    // 设置渲染的回调,window对象在哪,就回调到哪
    ffmpegHelper->setRenderFrame(renderFrame);
    // 初始化ffmpeg
    ffmpegHelper->prepare();

    env->ReleaseStringUTFChars(dataSource_, dataSource);
}

/**
 * 初始化ffmpeg
 */
void FfmpegHelper::initFfmpeg() {
    // 4.0.5 ffmpeg版本,与3.4的版本的写法还有点不一样
    // 初始化网络模块
    avformat_network_init();
    // 针对当前视频的总上下文 --- 第一个上下文
    formatContext = avformat_alloc_context();

    // 设置打开文件的超时时间
    AVDictionary *avDictionary = NULL;
    av_dict_set(&avDictionary, "timeout", "3000000", 0);
    // 打开文件
    int rst = avformat_open_input(&formatContext, dataSource, NULL, &avDictionary); //&avDictionary
    // 如果打开失败,return
    if (rst) {
        // 回调给java层
        if (javaCallHelper) {
            javaCallHelper->onError(THREAD_CHILD, ERROR_OPEN_FILE_FAILED);
        }
        return;
    }

    // 查找流
    rst = avformat_find_stream_info(formatContext, 0);
    LOGD("avformat_find_stream_info rst = %d", rst);
    if (rst < 0) {
        if (javaCallHelper) {
            javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_STREAM_FAILED);
        }
        return;
    }

    videoChannel = NULL;
    audioChannel = NULL;

    for (int i = 0; i < formatContext->nb_streams; i++) {
        AVCodecParameters *codecParameters = formatContext->streams[i]->codecpar;
        // 找到解码器
        AVCodec *decoder = avcodec_find_decoder(codecParameters->codec_id);
        if (!decoder) {
            if (javaCallHelper) {
                javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_DECODER_FAILED);
            }
            return;
        }

        // 创建解码器上下文
        AVCodecContext *decoderContext = avcodec_alloc_context3(decoder);
        if (!decoderContext) {
            if (javaCallHelper) {
                javaCallHelper->onError(THREAD_CHILD, ERROR_ALLOC_CODEC_CONTEXT_FAILED);
            }
            return;
        }

        // 把解码器信息赋值给上下文
        avcodec_parameters_to_context(decoderContext, codecParameters);

        // 重要,设置解码线程的个数,提升软解码速度
        if (codecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
            // 视频解码,多线程软解码提高解码效率
            decoderContext->thread_count = 4;       // 软解码线程,
        }

        // 打开解码器
        if (avcodec_open2(decoderContext, decoder, NULL) != 0) {
            if (javaCallHelper) {
                javaCallHelper->onError(THREAD_CHILD, ERROR_OPEN_DECODER_FAILED);
            }
            return;
        }

        // 视频
        if (codecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
            // 这里就拿到了帧率
            AVRational rational = formatContext->streams[i]->avg_frame_rate;
            int fps = av_q2d(rational);

            AVRational time_base = formatContext->streams[i]->time_base;  // 时间显示的单位

            // 视频通道的初始化 与 帧率设置(播放控制时用得到)
            videoChannel = new VideoChannel(i, decoderContext, javaCallHelper, time_base);
            videoChannel->setFps(fps);

            // 把回调设置过去
            if (renderFrame) {
                videoChannel->setRenderFrame(renderFrame);
            }
        }
            // 音频
        else if (codecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
            AVRational time_base = formatContext->streams[i]->time_base;  // 时间显示的单位
            audioChannel = new AudioChannel(i, decoderContext, javaCallHelper, time_base);
        } else {
            avcodec_parameters_free(&codecParameters);
        }
    }

    // 把audioChannel对象 注入到 videoChannel中,是为了做音视频同步
    if (videoChannel && audioChannel) {
        videoChannel->setAudioChannel(audioChannel);
    }

    // 再判断一下音频/视频是否找到了
    if (!videoChannel && !audioChannel) {
        if (javaCallHelper) {
            javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_NO_MEDIA);
        }
        return;
    }

    // 回调java层,ffmpeg已经初始化好了,可以开始播放了
    javaCallHelper->onPrepare(THREAD_CHILD);
}

/**
 * 开始解码,把packet放到对应的packet queue中
 */
void FfmpegHelper::startDecode() {
    int ret = 0;
    AVPacket *packet = NULL;

    while (isPlaying) {

        // 防止解码的packet太多,导致内存溢出
        ......

        // 相当于new 一个packet对象
        packet = av_packet_alloc();
        // 从媒体中读取到音频、视频包
        ret = av_read_frame(formatContext, packet);

        // 读取成功
        if (ret == 0) {
            // 把数据包放入队列
            // 视频包
            if (videoChannel && packet->stream_index == videoChannel->channelId) {
                // 把pkt丢给videoChannel中的安全队列,剩下的由videoChannel来单独处理即可,责任清晰
                videoChannel->pkt_queue.put(packet);
            }
                // 音频包
            else if (audioChannel && packet->stream_index == audioChannel->channelId) {
                audioChannel->pkt_queue.put(packet);
            }
        } else if (ret == AVERROR_EOF) {
            // 读取完毕,但是不一定播放完毕
            if (videoChannel->pkt_queue.empty() && videoChannel->frame_queue.empty()
                && audioChannel->pkt_queue.empty() && audioChannel->frame_queue.empty()) {
                LOGD("播放完毕");
                break;
            }
            // 因为seek的存在,就算读取完毕,依然要循环去执行av_read_frame,否则seek无效
        } else {
            break;
        }
    }
    packet = NULL;
    isPlaying = false;

    pthread_exit(0);
}
VideoChannel.cpp
/**
 * 解码packet并放到frame queue中
 */
void VideoChannel::decodePacket() {
    LOGD("decodePacket start");
    // 子线程
    AVPacket *packet = 0;
    AVFrame *frame;
    int ret;

    while (isPlaying) {
        // 把队列中的数据读取到pkt_queue中
        ret = pkt_queue.get(packet);
        if (!isPlaying) {
            break;
        }

        if (!ret) {
            continue;
        }
        // 解码
        avcodec_send_packet(decoderContext, packet);
        frame = av_frame_alloc();
        ret = avcodec_receive_frame(decoderContext, frame);
        // packet资源释放
        releasePacket(packet);

        if (ret) {
            // 没有读到帧数据
            continue;
        }
        // 解码成功后,把frame对象放到frame队列中
        frame_queue.put(frame);

        // 如果frame队列大于100帧,则等一等
        while (isPlaying && frame_queue.size() >= 100) {
            av_usleep(10 * 1000);
            continue;
        }
    }
    // 释放packet
    releasePacket(packet);
    LOGD("decodePacket end");
}

/**
 * 从frame queue中获取frame对象,并传给window对象真正显示
 */
void VideoChannel::showFrame() {
    LOGD("show frame start");
    // 转换器上下文
    swsContext = sws_getContext(decoderContext->width, decoderContext->height,
                                decoderContext->pix_fmt, decoderContext->width,
                                decoderContext->height, AV_PIX_FMT_RGBA,
                                SWS_BILINEAR, 0, 0, 0);

    AVFrame *frame = NULL;
    int ret;

    long long start, end;

    double extra_delay, videoClock, timeDif;

    uint8_t *dst_data[4];
    // 每一行的首地址
    int dst_linesize[4];
    // 申请一张width * height, RGBA的图片内存大小 --- av_image_alloc不能放在while循环里,会内存溢出
    av_image_alloc(dst_data, dst_linesize,
                   decoderContext->width, decoderContext->height,
                   AV_PIX_FMT_RGBA, 1); // 1代码左对齐
    double delay = 0;
    while (isPlaying) {
        if (!isPlaying) {
            break;
        }

        ret = frame_queue.get(frame);
        if (!isPlaying) {
            break;
        }

        if (!ret) {
            continue;
        }

        // frame->data --- yuv数据
        // frame->linesize --- yuv 每一行的首地址
        // 0 --- y轴方向的偏移量
        // frame->height --- 帧高度
        // dst_data --- 转换后的rgba数据数组
        // dst_linesize --- rgba数据数组的每一行的首地址
        // yuv 转 rgba, 并把转换后的rgba数据,放在dst_data中

        start = getCurrentTimeUs();
        // 把frame中的数据,迁移到image对象中
        sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height,
                  dst_data, dst_linesize);

        // 把数据回调出去,在持有window处,真正来显示
        renderFrame(dst_data[0], dst_linesize[0], decoderContext->width, decoderContext->height);

        // 视频 与 音频 的时间戳 的差异, timeDif单位为 秒
        videoClock = frame->pts * av_q2d(timeBase);
        if (audioChannel) {
            timeDif = videoClock - audioChannel->colock;
        }

        end = getCurrentTimeUs();
        extra_delay =
                frame->repeat_pict / (double) (2 * fps);   // 如果没有重复帧,这个值为0;为重复帧时,导致的额外延时,单位 秒

        // 统一转换为单位 秒
        delay = (1.0f / fps) + extra_delay - (end - start) / 1000000.0f;
//        LOGD("YUV 2 RGBA cost = %lld, delay show = %d", (end - start), delay);
        delay = delay < 0 ? 0 : delay;

        // 根据timeDif来动态调整休眠时间
        adjustSleepTime(timeDif, delay);

        // 释放 frame, RGBA数据都出来了,frame就没有用了
        releaseFrame(frame);
    }

    releaseFrame(frame);
    av_freep(&dst_data[0]);
    isPlaying = false;

    LOGD("show frame end");
}
/**
 * 正在显示视频的处理方法,因为这个对象持有window对象
 * @param data
 * @param lineSize
 * @param w
 * @param h
 */
void renderFrame(uint8_t *data, int lineSize, int w, int h) {
//    LOGD("native-lib renderFrame");
    pthread_mutex_lock(&mutex);

    if (!window) {
        pthread_mutex_unlock(&mutex);
        return;;
    }

    // 渲染
    // nativeWindow缓冲区大小设置
    ANativeWindow_setBuffersGeometry(window, w, h,
                                     WINDOW_FORMAT_RGBA_8888);
    ANativeWindow_Buffer outBuffer;

    // 锁住window ---- 把window 和 outBuffer 绑定
    if (ANativeWindow_lock(window, &outBuffer, 0)) {
        // 如果锁定失败的处理
        ANativeWindow_release(window);
        window = 0;
        pthread_mutex_unlock(&mutex);
        return;
    }

    // 进行渲染
    uint8_t *firstWindows = static_cast<uint8_t *>(outBuffer.bits);
    // 输入源Rgba的
    uint8_t *src_data = data;
    // 拿到一行有多少个字节,需要 *4(stride 代表的是像素数量,一个像素4个字节)
    int dstStride = outBuffer.stride * 4;
    int srcStride = lineSize;

    // 一行一行copy的原因,是因为window窗体大小,与image buffer的大小,有可能不一致。
    // 是一个数据对齐的问题。
    for (int y = 0; y < outBuffer.height; y++) {
        // 内存拷贝,来进行渲染, 一行一行地copy, 长度以目的地为准
        memcpy(firstWindows + y * dstStride, src_data + y * srcStride, dstStride);
    }
    // 解锁
    ANativeWindow_unlockAndPost(window);
    pthread_mutex_unlock(&mutex);
}
上一篇下一篇

猜你喜欢

热点阅读