AndroidNDK工程FFmpeg(一)–Mac下编译 Android使用FFmpeg–Shell脚本编写与执行编译FFmpeg库
AndroidNDK工程FFmpeg(二)–AndroidStudio下创建FFmpeg的NDK工程
前面两篇文章介绍了构建FFmpeg的AndroidStudio的NDK工程,那么我们这次就根据该NDK工程制作一个简易的视频播放器
FFmpeg播放视频的流程或者思路8步:
1、获取总上下文
2、遍历流
3、解码器上下文
4、获取到解码器
5、流中读取packet
6、packet转换成frame
7、统一转换成可显示的格式
8、输出到相应的设备中
一、在布局文件中添加一个SurfaceView和一个播放的按钮。我们把视频渲染到SurfaceView上。
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="200dp"
/>
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="open"
android:text="打开"
/>
二、创建一个播放的 native方法,方法实现如下,大部分内容都在该方法里,主要是FFmpeg API的使用,其中有详细注释。
#include <jni.h>
#include <string>
#include <android/native_window_jni.h>
#include <zconf.h>
extern "C" {//extern "C是为了在C++引用C语言中的函数和变量
#include <libavcodec/avcodec.h> //引入ffmpeg的包
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
#include <libavformat/avformat.h>
}
extern "C" JNIEXPORT void JNICALL
Java_com_goodboy_mileplayer_milePlayer_native_1start(JNIEnv *env, jobject instance, jstring path_,
jobject surface) {
// 获取视频路径
const char *path = env->GetStringUTFChars(path_,0);
//加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持 其实4.0以后不推荐使用,我是初学者还没找到更好的方法
avformat_network_init();
//获取视频格式的上下文
AVFormatContext * formatContext = avformat_alloc_context();
//字典存储相关参数
AVDictionary *opts = NULL;
//设置超时3秒 后面为0的情况下就是把设置的值存到字典里
av_dict_set(&opts,"timeout","3000000",0);
//打开视频文件//ret为零 表示成功
int ret = avformat_open_input(&formatContext,path,NULL,&opts);
//通知FFmpeg寻找视频中的流,视频中一般最少有两个流,视频流、音频流
avformat_find_stream_info(formatContext, NULL);
//定义视频流索引变量 视频时长(单位:微秒us,转换为秒需要除以1000000)
int vidio_stream_idx=-1;
//找到视频流索引
for (int i = 0; i < formatContext->nb_streams; ++i) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
vidio_stream_idx=i;
break;
}
}
//拿到视频流的解码参数
AVCodecParameters *codecpar = formatContext->streams[vidio_stream_idx]->codecpar;
//找到解码器
AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
//创建解码器的上下文
AVCodecContext *codecContext = avcodec_alloc_context3(dec);
//把解码参数赋值到解码器上下文里
avcodec_parameters_to_context(codecContext, codecpar);
//打开解码器
avcodec_open2(codecContext, dec, NULL);
//读取包 解码yuv数据,到AVPacket包里
AVPacket *packet = av_packet_alloc();
//转换上下文 像素数据
SwsContext *sws_ctx = sws_getContext(
codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->width, codecContext->height, AV_PIX_FMT_RGBA,
SWS_BILINEAR, 0, 0, 0);
//SWS_BILINEAR 取中间值,可以快速的 重视速度,重视质量 有一篇文章介绍这个属性https://blog.csdn.net/leixiaohua1020/article/details/12029505
//用来把视频渲染到surface上
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
// 视频缓冲区 视频渲染都会有一个缓冲区
ANativeWindow_Buffer outBuffer;
//创建新的窗口用于视频显示
// ANativeWindow
int frameCount = 0;
ANativeWindow_setBuffersGeometry(nativeWindow, codecContext->width,
codecContext->height,
WINDOW_FORMAT_RGBA_8888);
//循环读取视频到packet
while (av_read_frame(formatContext, packet)>=0) {
//提供原始数据包数据作为解码器的输入。 维护一个队列,并把packet传入
avcodec_send_packet(codecContext, packet);
AVFrame *frame = av_frame_alloc();
//从队列里获取packet转化成frame
ret = avcodec_receive_frame(codecContext, frame);
if (ret == AVERROR(EAGAIN)) {//继续读取
//需要更多数据
continue;
} else if (ret < 0) {//视频结束了
break;
}
//接收的容器
uint8_t *dst_data[0];
int dst_linesize[0];
//分配具有大小width和height以及编码格式是传进来AV_PIX_FMT_RGBA的图像,以及相应地填充指针和行大小。
//dst_data 是一个长度为4的数组 存图片的R G B A 一个图片本身就是一个数组
//最后的参数1 是对齐方式 左对齐
av_image_alloc(dst_data, dst_linesize,
codecContext->width, codecContext->height, AV_PIX_FMT_RGBA, 1);
if (packet->stream_index == vidio_stream_idx) {
//非零 正在解码
if (ret==0) {
//播放视频的流程 用解码器读取到packet包里-->AvFrame yum -->image -->dest_data -->渲染到sufaceView
// 绘制之前 配置一些信息 比如宽高 格式
// 绘制
ANativeWindow_lock(nativeWindow, &outBuffer, NULL);
// h 264 ----yuv RGBA
//转为指定的YUV420P
sws_scale(sws_ctx,
reinterpret_cast<const uint8_t *const *>(frame->data), frame->linesize, 0,
frame->height,
dst_data, dst_linesize);
//rgb_frame是有画面数据
uint8_t *dst= (uint8_t *) outBuffer.bits;
// 拿到一行有多少个字节 RGBA
int destStride=outBuffer.stride*4;
uint8_t *src_data = dst_data[0];
int src_linesize = dst_linesize[0];
uint8_t *firstWindown = static_cast<uint8_t *>(outBuffer.bits);
//渲染 把图像一行一行的拷贝到缓存区
for (int i = 0; i < outBuffer.height; ++i) {
memcpy(firstWindown + i * destStride, src_data + i * src_linesize, destStride);
}
ANativeWindow_unlockAndPost(nativeWindow);
av_usleep(1000 * 16);
av_frame_free(&frame);
}
}
}
ANativeWindow_release(nativeWindow);
avcodec_close(codecContext);
avformat_free_context(formatContext);
//avformat_close_input(&formatContext); //不知道这一步骤是不是必须的
env->ReleaseStringUTFChars(path_, path);
}
最终效果:我自己录的键盘,只是个截图