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表示播放成功