【android】ffmpeg实现播放视频

ffmpeg播放视频

ps:实现ffmpeg的视频解码转为yuv和rgba,并用rgba进行输出。

下面将对实现的native_play进行解释:

0. 导入部分

#include <jni.h>
#include <string>
#include <log.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include "AAudioRender.h"
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <unistd.h> // 用于 sleep 函数


extern "C" {
#include <libavutil/avutil.h> // 包含avutil库以访问av_version_info()
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

导入JNI接口以及AAudioRender.h类用于视频输出以及其他多线程相关函数库尝试多线程编程。
ffmpeg的库要声明使用C来编译,否则以c++的方式会报错链接不到库

1. 视频播放部分

 const char *file_uri = env->GetStringUTFChars(file, nullptr);
    LOGI(LOG_TAG, "开始播放视频:%s", file_uri);

    // 打开视频文件
    AVFormatContext *pFormatCtx = NULL;
    if (avformat_open_input(&pFormatCtx, file_uri, NULL, NULL) != 0) {
        LOGE(LOG_TAG, "无法打开视频文件:%s", file_uri);
        return -1;
    }
    LOGI(LOG_TAG, "成功打开视频文件:%s", file_uri);

2. 获取流信息并定位流的起始地址

 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE(LOG_TAG, "无法找到流信息");
        return -1;
    }
    LOGI(LOG_TAG, "找到视频流信息");
    
     int videoStreamIndex = -1;
    // 查找视频流的索引位置
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i; //循环获取当前流信息,只要是信息流的类型就代表起始地址,break掉
            break;
        }
    }
    if (videoStreamIndex == -1) {
        LOGE(LOG_TAG, "无法找到视频流");
        return -1;
    }
    LOGI(LOG_TAG, "找到视频流,索引:%d", videoStreamIndex);

3. 对获取到的视频流起始地址开始编解码

// 获取视频流的编解码器
    AVCodec *pCodec = avcodec_find_decoder(pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id);
    if (!pCodec) {
        LOGE(LOG_TAG, "无法找到解码器");
        return -1;
    }
    LOGI(LOG_TAG, "找到解码器");

    // 创建解码器上下文
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx) {
        LOGE(LOG_TAG, "无法分配解码器上下文");
        return -1;
    }
    LOGI(LOG_TAG, "解码器上下文已分配");

    // 复制视频流的编解码器参数到解码器上下文
    if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStreamIndex]->codecpar) < 0) {
        LOGE(LOG_TAG, "无法将解码器参数复制到解码器上下文");
        return -1;
    }
    LOGI(LOG_TAG, "解码器参数已复制到解码器上下文");

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        LOGE(LOG_TAG, "无法打开解码器");
        return -1;
    }
    LOGI(LOG_TAG, "解码器已打开");

    AVFrame *pFrame = av_frame_alloc();
    AVPacket packet;

4. 视频渲染的前置工作

// 创建 ANWRender 实例
    ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
    ANWRender renderer(window);

    // 获取视频宽高
    int videoWidth = pCodecCtx->width;
    int videoHeight = pCodecCtx->height;
    LOGI(LOG_TAG, "视频宽度:%d,视频高度:%d", videoWidth, videoHeight);

    // 初始化渲染器
    int initResult = renderer.init(videoWidth, videoHeight);
    if (initResult != 0) {
        LOGE(LOG_TAG, "渲染器初始化失败");
        return -1;
    }
    LOGI(LOG_TAG, "渲染器已初始化");

5.渲染视频从yuv转为rgba

ps:这里直接把输出的yuv转为rgba帧调用ANWRender中的render函数进行逐帧渲染。

// 解码并渲染视频帧
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        if (packet.stream_index == videoStreamIndex) {
            // 解码视频帧
            if (avcodec_send_packet(pCodecCtx, &packet) < 0) {
                LOGE(LOG_TAG, "发送视频帧数据包失败");
                return -1;
            }

            while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
                // 将 YUV 帧转换为 RGBA 帧
                struct SwsContext *sws_ctx = sws_getContext(
                        pCodecCtx->width,
                        pCodecCtx->height,
                        pCodecCtx->pix_fmt,
                        pCodecCtx->width,
                        pCodecCtx->height,
                        AV_PIX_FMT_RGBA,
                        SWS_BILINEAR,
                        NULL,
                        NULL,
                        NULL
                );

                AVFrame *pFrameRGBA = av_frame_alloc();
                int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width,
                                                        pCodecCtx->height, 1);
                uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
                av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
                                     pCodecCtx->width, pCodecCtx->height, 1);

                sws_scale(
                        sws_ctx,
                        (uint8_t const *const *) pFrame->data,
                        pFrame->linesize,
                        0,
                        pCodecCtx->height,
                        pFrameRGBA->data,
                        pFrameRGBA->linesize
                );

                // 渲染 RGBA 图像数据到 Surface
                uint8_t* rgba = pFrameRGBA->data[0];
                renderer.render(rgba);

                // 释放资源
                av_freep(&buffer);
                av_frame_free(&pFrameRGBA);
                sws_freeContext(sws_ctx);
            }

            av_frame_unref(pFrame); // 释放解码后的帧资源
        }
        av_packet_unref(&packet);
    }

6. 渲染结束和内存资源回收

// 释放资源
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    ANativeWindow_release(window);
    env->ReleaseStringUTFChars(file, file_uri);

    LOGI(LOG_TAG, "视频播放完成");
    return 0; // 返回0表示播放成功

7. 输出样例

输出样例图

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最弱无胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值