前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NDK--利用FFmpeg进行视频解码

NDK--利用FFmpeg进行视频解码

作者头像
aruba
发布2020-07-02 17:11:31
8820
发布2020-07-02 17:11:31
举报
文章被收录于专栏:android技术android技术
对于普通人而言,视频文件有许多格式,如mp4,rmvb,avi等,这存在的一个误区,让人们谈到视频就想到这些文件格式,而在音视频开发中,我们把这些文件格式称为封装格式,这些封装格式中,不仅仅包含着视频流,还包含着音频流,字幕流等,而视频流都按照视频编码标准进行编码,下面是视频编码标准

名称 推出机构 推出时间 目前使用领域 HEVC(H.265) MPEG/ITU-T 2013 研发中 H.264 MPEG/ITU-T 2003 各个领域 MPEG4 MPEG 2001 不温不火 MPEG2 MPEG 1994 数字电视 VP9 Google 2013 研发中 VP8 Google 2008 不普及 VC-1 Microsoft Inc. 2006 微软平台

那么为什么要编码呢?我们知道所有的颜色都可以用三原色(Red,Green,Blue)来合成,任何一个图像都可以由RGB组成,那么一个像素点的RGB该如何表示呢?对于一幅图像,一般使用整数表示方法来进行描述:

·整数表示:取值范围为0~255或者00~FF,8个比特表示一个子像素,32个比特表示一个像素,这就是类似于某些平台上表示图像格式的 RGBA_8888数据格式。比如,Android平台上RGB_565的表示方法为16比特模式表示一个像素,R用5个比特来表示,G用6个比特来表示,B用5个比特来表示。

那么,计算一张 1280×720的RGBA_8888图像的大小,可采用如下方式:

1280 * 720 * 4 = 3.516MB

很明显如果一个视频使用RGB来存储,那么它的文件大小将非常庞大,如果fps(1秒的视频帧数目)是24,按照一般电影的长度90分钟来计算,RGB数据格式来表示的话,它的文件大小为:

3.516MB * 24fps * 90min * 60s = 455.7GB

后来,生物学家利用人眼的特性,推出了YUV,YUV主要应用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需要占用极少的频宽(RGB要求三个独立的视频信号同时传输)。

“Y”表示明亮度 (Luminance或Luma),也称灰阶值;而“U”和“V”表示的则是色度 (Chrominance或Chroma),它们的作用是描述影像的色彩及饱和度, 用于指定像素的颜色。

YUV最常用的采样格式是4:2:0

如果某一行是4:2:0,那么其下一行就是4:0:2,再下一行是 4:2:0,以此类推。对于每个色度分量来说,水平方向和竖直方向的 抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,8×4的一张图片需要占用48字节的内存

相较于RGB,我们可以计算一帧为1280×720的视频帧,用 YUV420P的格式来表示,其数据量的大小如下:

1280 * 720 * 1 + 1280 * 720 * 0.5 = 1.318MB

如果fps(1秒的视频帧数目)是24,按照一般电影的长度90分钟来 计算,那么这部电影用YUV420P的数据格式来表示的话,其数据量的 大小就是:

1.318MB * 24fps * 90min * 60s = 166.8GB

虽然相比于RGB而言,文件体积已经小了很多,但仅用这种方式来存储电影肯定是不可行的,更别说在网络上进行流媒体播放了,那么如何对电影进行存储以及流媒体播放呢?答案是 需要进行视频编码,即视频压缩
压缩分为有损压缩和无损压缩,无损压缩比2:1 ~ 3:1,有损压缩比10:1 ~ 20:1,一般都为有损压缩。主要针对以下几点进行压缩:

○ 空间冗余:图像相邻像素之间有较强的相关性 ○ 时间冗余:视频序列的相邻图像之间内容相似 ○ 编码冗余:不同像素值出现的概率不同 ○ 视觉冗余:人的视觉系统对某些细节不敏感 ○ 知识冗余:规律性的结构可由先验知识和背景知识得到

因此有了我们开始所说的视频编码标准,对视频进行压缩(编码)来保存文件,播放时对视频解压缩(解码)成YUV格式进行播放
接下来,利用之前编译集成好FFmpeg的as项目,初探视频解码:将视频转码成yuv格式
在手机中传入一个MP4视频:
在java中传入原始视频路径和转码视频路径:
package com.aruba.ffmpegapplication;

import android.os.Bundle;
import android.os.Environment;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    public native void decode(String inputFilePath, String outputFilePath);

    public void click(View view) {
        File input = new File(Environment.getExternalStorageDirectory(), "input.mp4");
        File output = new File(Environment.getExternalStorageDirectory(), "output.yuv");
        decode(input.getAbsolutePath(), output.getAbsolutePath());
    }
}
编写相应的native方法:
#include <jni.h>
#include <string>
#include <android/log.h>

extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
}

#define  LOG_TAG    "aruba"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

extern "C"
JNIEXPORT void JNICALL
Java_com_aruba_ffmpegapplication_MainActivity_decode(JNIEnv *env, jobject instance,
                                                     jstring inputFilePath_,
                                                     jstring outputFilePath_) {
    const char *inputFilePath = env->GetStringUTFChars(inputFilePath_, 0);
    const char *outputFilePath = env->GetStringUTFChars(outputFilePath_, 0);

    //注册FFmpeg中各大组件
    av_register_all();

    //打开文件
    AVFormatContext *formatContext = avformat_alloc_context();
    if (avformat_open_input(&formatContext, inputFilePath, NULL, NULL) != 0) {
        LOGE("打开失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //将文件信息填充进AVFormatContext
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        LOGE("获取文件信息失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //获取视频流的编解码器上下文
    AVCodecContext *codecContext = NULL;
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {//如果是视频流
            codecContext = formatContext->streams[i]->codec;
            break;
        }
    }

    if (codecContext == NULL) {
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //根据编解码器上下文的id获取视频流解码器
    AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
    //打开解码器
    if (avcodec_open2(codecContext, codec, NULL) < 0) {
        LOGE("解码失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //开始读每一帧
    //存放压缩数据
    AVPacket *pkt = (AVPacket *) (av_malloc(sizeof(AVPacket)));
    av_init_packet(pkt);

    //存放解压数据
    AVFrame *picture = av_frame_alloc();

    //存放转码数据
    AVFrame *picture_yuv = av_frame_alloc();
    //为转码数据分配内存
    uint8_t *data_size = (uint8_t *) (av_malloc(
            (size_t) avpicture_get_size(AV_PIX_FMT_YUV420P, codecContext->width,
                                        codecContext->height)));
    avpicture_fill((AVPicture *) picture_yuv, data_size, AV_PIX_FMT_YUV420P, codecContext->width,
                   codecContext->height);

    int picture_ptr = 0;

    //转码组件上下文,前三个参数为原视频的宽高和编码,后三个为转码后的视频宽高和编码,还可以传入过滤器对视频做处理,这边不做处理
    SwsContext *swsContext = sws_getContext(codecContext->width, codecContext->height,
                                            codecContext->pix_fmt,
                                            codecContext->width, codecContext->height,
                                            AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL
    );

    //文件
    FILE *output_file = fopen(outputFilePath, "wb");

    while (av_read_frame(formatContext, pkt) == 0) {//读到每一帧的压缩数据存放在AVPacket

        //解码
        avcodec_decode_video2(codecContext, picture, &picture_ptr, pkt);

        if (picture_ptr > 0) {
            LOGE("picture_ptr %d", picture_ptr);

            //转码 data中存放着真实数据,linesize为一行的数据,0为转码起始位置,高度为整个画面高
            sws_scale(swsContext, picture->data, picture->linesize, 0, picture->height,
                      picture_yuv->data, picture_yuv->linesize);

            //一帧的数据大小
            size_t size_y = codecContext->width * codecContext->height;
            //写文件 y:u:v为4:1:1
            //写y
            fwrite(picture_yuv->data[0], sizeof(uint8_t), size_y, output_file);
            //写u
            fwrite(picture_yuv->data[1], sizeof(uint8_t), size_y / 4, output_file);
            //写v
            fwrite(picture_yuv->data[2], sizeof(uint8_t), size_y / 4, output_file);
        }

        av_free_packet(pkt);
    }

    //关闭文件
    fclose(output_file);
    //释放资源
    sws_freeContext(swsContext);
    av_frame_free(&picture_yuv);
    av_frame_free(&picture);
    avcodec_close(codecContext);
    avformat_free_context(formatContext);
    env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
    env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
}
最后授予APP读写权限,生成的yuv文件:
很明显,yuv文件体积较原视频大很多,视频解码转码的课题就到此结束了,之后的文章会介绍如何原生绘制视频。
项目地址:https://gitee.com/aruba/FFmpegApplication.git
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 对于普通人而言,视频文件有许多格式,如mp4,rmvb,avi等,这存在的一个误区,让人们谈到视频就想到这些文件格式,而在音视频开发中,我们把这些文件格式称为封装格式,这些封装格式中,不仅仅包含着视频流,还包含着音频流,字幕流等,而视频流都按照视频编码标准进行编码,下面是视频编码标准
    • 那么为什么要编码呢?我们知道所有的颜色都可以用三原色(Red,Green,Blue)来合成,任何一个图像都可以由RGB组成,那么一个像素点的RGB该如何表示呢?对于一幅图像,一般使用整数表示方法来进行描述:
      • 那么,计算一张 1280×720的RGBA_8888图像的大小,可采用如下方式:
        • 很明显如果一个视频使用RGB来存储,那么它的文件大小将非常庞大,如果fps(1秒的视频帧数目)是24,按照一般电影的长度90分钟来计算,RGB数据格式来表示的话,它的文件大小为:
          • 后来,生物学家利用人眼的特性,推出了YUV,YUV主要应用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需要占用极少的频宽(RGB要求三个独立的视频信号同时传输)。
            • YUV最常用的采样格式是4:2:0
              • 相较于RGB,我们可以计算一帧为1280×720的视频帧,用 YUV420P的格式来表示,其数据量的大小如下:
                • 如果fps(1秒的视频帧数目)是24,按照一般电影的长度90分钟来 计算,那么这部电影用YUV420P的数据格式来表示的话,其数据量的 大小就是:
                  • 虽然相比于RGB而言,文件体积已经小了很多,但仅用这种方式来存储电影肯定是不可行的,更别说在网络上进行流媒体播放了,那么如何对电影进行存储以及流媒体播放呢?答案是 需要进行视频编码,即视频压缩
                    • 压缩分为有损压缩和无损压缩,无损压缩比2:1 ~ 3:1,有损压缩比10:1 ~ 20:1,一般都为有损压缩。主要针对以下几点进行压缩:
                      • 因此有了我们开始所说的视频编码标准,对视频进行压缩(编码)来保存文件,播放时对视频解压缩(解码)成YUV格式进行播放
                      • 接下来,利用之前编译集成好FFmpeg的as项目,初探视频解码:将视频转码成yuv格式
                        • 在手机中传入一个MP4视频:
                          • 在java中传入原始视频路径和转码视频路径:
                            • 编写相应的native方法:
                              • 最后授予APP读写权限,生成的yuv文件:
                                • 很明显,yuv文件体积较原视频大很多,视频解码转码的课题就到此结束了,之后的文章会介绍如何原生绘制视频。
                                  • 项目地址:https://gitee.com/aruba/FFmpegApplication.git
                                  相关产品与服务
                                  图像处理
                                  图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档