NDK之FFmpeg视频解码

在学了在学了! 2020-08-26 17:39:57 4052

https://blog.csdn.net/ccj659/article/details/52636772
NDK之FFmpeg视频解码
FFmpeg库简介
avcodec:编解码,包含
avformate: 封装格式处理
avfilter:滤镜特效处理
avdevice:输入输出设备
avutil:工具库
swresample:音频采样处理
swscale:视频像素格式转换,缩放等
FFmpeg解码流程
av_register_all(); //注册所有组件。
AVFormatContext //获取上下文等信息//是封装格式上下文结构体,统领全局,保存视频文件的封装格式信息
avformat_open_input(&pFormCtx,input_cstr,NULL,NULL) //打开输入文件
avformat_find_stream_info(pFormCtx,NULL)获取文件信息,
AVStream,AVCodecContext获取流索引位置
avcodec_find_decoder(avCodeCtx->codec_id)查找解码器。
avcodec_open2(avCodeCtx,avCode,NULL)//打开解码器。
AVPacket,AVFrame//获取帧数据,申请空间
av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height))//缓冲区分配内存
avpicture_fill((AVPicture *)yuvFrame, out_buffer, AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height);//初始化缓冲区
while(av_read_frame(pFormatCtx,packet)//一阵一阵读取压缩的视频数据AVPacket
avcodec_decode_video2(pCodeCtx, frame, &got_frame, packet)//解码AVPacket->AVFrame
sws_scale//转为指定的YUV420P像素帧
fwrite(yuvFrame->data[0], 1, y_size, fp_yuv);/向YUV文件保存解码之后的帧数据//AVFrame->YUV//一个像素包含一个Y
释放相关内存av_free_packet,fclose,av_frame_free,avcodec_close…

FFmpeg解码的数据结构(主要的namespace)
AVFormatContext 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装
格式相关信息。
iformat:输入视频的AVInputFormat
nb_streams :输入视频的AVStream 个数
streams :输入视频的AVStream []数组
duration :输入视频的时长(以微秒为单位)
bit_rate :输入视频的码率
AVInputFormat 每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。
name:封装格式名称
long_name:封装格式的长名称
extensions:封装格式的扩展名
id:封装格式ID
一些封装格式处理的接口函数
FFmpeg数据结构分析
AVStream 视频文件中每个视频(音频)流对应一个该结构体。
id:序号
codec:该流对应的AVCodecContext
time_base:该流的时基
r_frame_rate:该流的帧率
AVCodecContext编码器上下文结构体,保存了视频(音频)编解码相关信息。
codec:编解码器的AVCodec
width, height:图像的宽高(只针对视频)
pix_fmt:像素格式(只针对视频)
sample_rate:采样率(只针对音频)
channels:声道数(只针对音频)
sample_fmt:采样格式(只针对音频)
AVCodec 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
name:编解码器名称
long_name:编解码器长名称
type:编解码器类型
id:编解码器ID
一些编解码的接口函数
AVPacket 存储一帧压缩编码数据。
pts:显示时间戳
dts :解码时间戳
data :压缩编码数据
size :压缩编码数据大小
stream_index :所属的AVStream
AVFrame存储一帧解码后像素(采样)数据。
data:解码后的图像像素数据(音频采样数据)。
linesize:对视频来说是图像中一行像素的大小;对音频来说是音频帧的大小。
width, height:图像的宽高(只针对视频)。
key_frame:是否为关键帧(只针对视频) 。
pict_type:帧类型(只针对视频) 。例如I,P,B。
FFmpeg解码参照
开发过程中可参考 ffmpeg-2.6.9\doc\examples中的例子.
在该例子文件夹下中 有音视频的读写, 过滤处理,转码等相关例子,大家可以进行参考学习.

FFmpeg的NDK视频解码播放实践
在本例中 以视频解码播放为例,在下手之前,请搞懂 FFmpeg解码的数据结构中的命名空间相关的属性以及作用.
1.编译ffmpeg成so库
在linux中编译ffmpeg
1.编写shell脚本文件
2.配置configuration
3.给文件权限:chmod 777 android_build.sh
4.执行 ./android_build.sh

2.编译libyuv库
1.下载libyuv的库,大家克隆即可https://chromium.googlesource.com/external/libyuv
2.执行ndk-build(linux需要提前配置ndk环境)
3.新建工程,导入库

4.配置工程环境
1.add native support.请参考 eclipse搭建NDK开发环境
2.配置Android.mk application.mk 请参考项目ffmpeg-palyer
3.编写本地方法,并实现C代码,生成so.
1.用自定义的SurfaceView 作为视频渲染载体.
2.编写本地方法,实现代码.

    public class CcjPlayer {
        //视频
        public native void render(String input,Object surface);
        //音频
        public native void sound(String input,String output);

        //注意加载库的顺序
        static{
            System.loadLibrary("avutil-54");
            System.loadLibrary("swresample-1");
            System.loadLibrary("avcodec-56");
            System.loadLibrary("avformat-56");
            System.loadLibrary("swscale-3");
            System.loadLibrary("postproc-53");
            System.loadLibrary("avfilter-5");
            System.loadLibrary("avdevice-56");
            System.loadLibrary("myffmpeg");
        }
    }

3.生成头文件,并实现方法(实现思路和流程请参考FFmpeg解码流程).
/*

  • Class: com_ccj_ffmpeg_CcjPlayer
  • Method: render
  • Signature: (Ljava/lang/String;Ljava/lang/Object;)V
    /
    JNIEXPORT void JNICALL Java_com_ccj_ffmpeg_CcjPlayer_render
    (JNIEnv
    env, jobject jobj, jstring input_jstr, jobject surface){
    const char input_cstr = (env)->GetStringUTFChars(env,input_jstr,NULL);
    //1.注册组件
    av_register_all();

    //封装格式上下文
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    
    //2.打开输入视频文件
    if(avformat_open_input(&pFormatCtx,input_cstr,NULL,NULL) != 0){
        LOGE("%s","打开输入视频文件失败");
        return;
    }
    //3.获取视频信息
    if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
        LOGE("%s","获取视频信息失败");
        return;
    }
    
    //视频解码,需要找到视频对应的AVStream所在pFormatCtx->streams的索引位置
    int video_stream_idx = -1;
    int i = 0;
    for(; i < pFormatCtx->nb_streams;i++){
        //根据类型判断,是否是视频流
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            video_stream_idx = i;
            break;
        }
    }

/4.获取视频解码器

    AVCodecContext *pCodeCtx = pFormatCtx->streams[video_stream_idx]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
    if(pCodec == NULL){
        LOGE("%s","无法解码");
        return;
    }

/5.打开解码器

    if(avcodec_open2(pCodeCtx,pCodec,NULL) < 0){
        LOGE("%s","解码器无法打开");
        return;
    }

    //编码数据
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    //像素数据(解码数据)
    AVFrame *yuv_frame = av_frame_alloc();
    AVFrame *rgb_frame = av_frame_alloc();

    /*//ScaleImg視頻縮放
    if(ScaleImg(pCodeCtx,yuv_frame,rgb_frame,pCodeCtx->width/6,pCodeCtx->width/6)!=1){
        LOGE("%s","縮放失敗");
        return;
    }*/

    //native绘制
    //窗体

    ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env,surface);

    //绘制时的缓冲区
    ANativeWindow_Buffer outBuffer;

    int len ,got_frame, framecount = 0;

//6.一阵一阵读取压缩的视频数据AVPacket

while(av_read_frame(pFormatCtx,packet) >= 0){
    //解码AVPacket->AVFrame
    len = avcodec_decode_video2(pCodeCtx, yuv_frame, &got_frame, packet);

    //Zero if no frame could be decompressed
    //非零,正在解码
    if(got_frame){
        LOGI("解码%d帧",framecount++);
        //lock
        //设置缓冲区的属性(宽、高、像素格式)
        ANativeWindow_setBuffersGeometry(nativeWindow, pCodeCtx->width, pCodeCtx->height,WINDOW_FORMAT_RGBA_8888);
        ANativeWindow_lock(nativeWindow,&outBuffer,NULL);

        //设置rgb_frame的属性(像素格式、宽高)和缓冲区
        //rgb_frame缓冲区与outBuffer.bits是同一块内存
        avpicture_fill((AVPicture *)rgb_frame, outBuffer.bits, AV_PIX_FMT_RGBA, pCodeCtx->width, pCodeCtx->height);

        //YUV->RGBA_8888
        I420ToARGB(yuv_frame->data[0],yuv_frame->linesize[0],
                yuv_frame->data[2],yuv_frame->linesize[2],
                yuv_frame->data[1],yuv_frame->linesize[1],
                rgb_frame->data[0], rgb_frame->linesize[0],
                pCodeCtx->width,pCodeCtx->height);

        //unlock
        ANativeWindow_unlockAndPost(nativeWindow);

        usleep(1000 * 16);

    }

    av_free_packet(packet);
}

ANativeWindow_release(nativeWindow);
av_frame_free(&yuv_frame);
avcodec_close(pCodeCtx);
avformat_free_context(pFormatCtx);

(*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);

}

3.生成头文件,并实现方法(实现思路和流程请参考FFmpeg解码流程).
/**

  • 万能解码器,播放视频器
  • @author ccj
  • */

    public class MainActivity extends Activity {

    private CcjPlayer player;
    private VideoView videoView;
    private Spinner sp_video;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        videoView = (VideoView) findViewById(R.id.video_view);
        sp_video = (Spinner) findViewById(R.id.sp_video);
        player = new CcjPlayer();
        //选择 <item>input.mp4</item> <item>音频</item>
        String[] videoArray = getResources().getStringArray(R.array.video_list);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
                android.R.layout.simple_list_item_1, 
                android.R.id.text1, videoArray);
        sp_video.setAdapter(adapter);
    }
    
    public void mPlay(View btn){
        String video = sp_video.getSelectedItem().toString();
        if("音频".equals(video)){
            String input = new File(Environment.getExternalStorageDirectory(),"Live.mp3").getAbsolutePath();
            String output = new File(Environment.getExternalStorageDirectory(),"Live.pcm").getAbsolutePath();
            player.sound(input, output);
    
        }else{
            String input = new File(Environment.getExternalStorageDirectory(),video).getAbsolutePath();
            //Surface传入到Native函数中,用于绘制
            Surface surface = videoView.getHolder().getSurface();
            player.render(input, surface);
        }
    }
    }

    导入input.mp4到指定位置.播放即可.

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
在学了在学了!
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区