AndroidNDK工程FFmpeg(四)--FFmpeg视频解码之视频播放器

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);

}

最终效果:我自己录的键盘,只是个截图
在这里插入图片描述

代码下载

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值