1. FFmpeg视频编码

Android代码工程

iOS代码工程

1.1. 一、视频编码流程

1.1.1. 第一步:注册组件

av_register_all:例如:编码器、解码器等等。

// 第一步:注册组件
av_register_all();

1.1.2. 第二步:初始化封装格式上下文

// 第二步:初始化封装格式上下文
AVFormatContext *avformat_context = avformat_alloc_context();
const char *coutFilePath = env->GetStringUTFChars(out_file_path, NULL);
// iOS使用
// const char *coutFilePath = [outFilePath UTF8String];
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
// 设置视频压缩数据格式类型(h264、h265、mpeg2等等...)
avformat_context->oformat = avoutput_format;

1.1.3. 第三步:打开输出文件

// 第三步:打开输出文件
// 参数一:输出流
// 参数二:输出文件
// 参数三:权限->输出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
    __android_log_print(ANDROID_LOG_INFO, "main", "打开输出文件失败");
    // iOS使用
    // NSLog(@"打开输出文件失败");
    return;
}

1.1.4. 第四步:创建输出码流

// 第四步:创建输出码流
// 注意:创建了一块内存空间,并不知道他是什么类型流,希望他是视频流
AVStream *av_video_stream = avformat_new_stream(avformat_context, NULL);

1.1.5. 第五步:初始化编码器上下文

1. 获取编码器上下文

// 5.1 获取编码器上下文
AVCodecContext *avcodec_context = av_video_stream->codec;

2. 设置视频编码器ID

// 5.2 设置视频编码器ID
avcodec_context->codec_id = avoutput_format->video_codec;

3. 设置为视频编码器

// 5.3 设置为视频编码器
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;

4. 设置像素数据格式

// 5.4 设置像素数据格式
// 编码的是像素数据格式,视频像素数据格式为YUV420P(YUV422P、YUV444P等等...)
// 注意:这个类型是根据你解码的时候指定的解码的视频像素数据格式类型
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;

5. 设置视频尺寸

// 5.5 设置视频尺寸
avcodec_context->width = 640;
avcodec_context->height = 352;

6. 设置视频帧率

// 5.6 设置视频帧率
// 视频帧率:25fps(每秒25帧)
// 单位:fps,"f"表示帧数,"ps"表示每秒
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;

7. 设置视频码率

// 5.7 设置视频码率

//(1)什么是码率?
// 含义:每秒传送的比特(bit)数单位为 bps(Bit Per Second),比特率越高,传送数据速度越快。
// 单位:bps,"b"表示数据量,"ps"表示每秒。

//(2)什么是视频码率?
// 含义:视频码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。

//(3)视频码率计算如下?
// 基本的算法是:码率(kbps)= 视频大小 - 音频大小(bit位)/ 时间(秒)。
// 例如:Test.mov时间 = 24秒,文件大小(视频+音频) = 1.73MB。
// 视频大小 = 1.34MB(文件占比:77%)= 1.34MB * 1024 * 1024 * 8 / 24 = 字节大小 = 468365字节 = 468Kbps。
// 音频大小 = 376KB(文件占比:21%)。
// 计算出来的码率 : 468Kbps,K表示1000,b表示位(bit)。
// 总结:码率越大,视频越大。
avcodec_context->bit_rate = 468000;

8. 设置GOP

// 5.8 设置GOP
// 影响到视频质量问题,是一组连续画面

//(1)MPEG格式画面类型
// 3种类型:I帧、P帧、B帧。

//(2)I帧:
// 内部编码帧,是原始帧(原始视频数据)
// 是完整画面,是关键帧(必需的有,如果没有I,那么你无法进行编码,解码)。
// 视频第1帧:视频序列中的第一个帧始终都是I帧,因为它是关键帧。

//(3)P帧
// 向前预测帧
// 预测前面的一帧类型,处理前面的一阵数据(->I帧、B帧)。
// P帧数据根据前面的一帧数据进行处理得到了P帧。

//(4)B帧
// 前后预测帧(双向预测帧),前面一帧和后面一帧的差别。
// B帧压缩率高,但是对解码性能要求较高。

//(5)总结
// I只需要考虑自己 = 1帧,P帧考虑自己+前面一帧 = 2帧,B帧考虑自己+前后帧 = 3帧
// 说白了,P帧和B帧是对I帧压缩。
// 每250帧,插入1个I帧,I帧越少,视频越小
avcodec_context->gop_size = 250;

9. 设置量化参数

// 5.9 设置量化参数
// 数学算法(高级算法),量化系数越小,视频越是清晰
// 一般情况下都是默认值,最小量化系数默认值是10,最大量化系数默认值是51
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;

10. 设置b帧最大值

// 5.10 设置b帧最大值
// 设置不需要B帧
avcodec_context->max_b_frames = 0;

1.1.6. 第六步:查找视频编码器

// 第六步:查找视频编码器
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if (avcodec == NULL) {
    __android_log_print(ANDROID_LOG_INFO, "main", "找不到编码器");
    // iOS使用
    // NSLog(@"找不到编码器");
    return;
}

__android_log_print(ANDROID_LOG_INFO, "main", "编码器名称为:%s", avcodec->name);
// iOS使用
// NSLog(@"编码器名称为:%s", avcodec->name);

1. 出现问题

新建测试工程(稍后会介绍建工程测试),代码运行到这一步会出现“找不到编码器”,因为编译库没有依赖x264库(默认情况下FFmpeg没有编译进行h264库)。

2. 解决问题

(1) 下载源码

x264库,翻墙更快。

git clone https://code.videolan.org/videolan/x264.git
(2) 下载ndk

https://developer.android.google.cn/ndk/downloads/older_releases.html

我这里使用的是ndkr10e

在x264源码的同目录下新建ndk文件交,将下载好的ndk放入ndk文件夹。

(3) 编译x264脚本

编译x264的.a静态库,指定编译平台类型:iOS平台、安卓平台、Mac平台、Windows平台等等。

android_build_x264.sh是编译脚本,将编译脚本放在和源码的同一目录,执行:

sh android-build-x264.sh

执行过程会提示开机密码,看到Android h264 builds finished说明编译成功。

(4) 编译FFmpeg

修改Android的FFmpeg动态库编译脚本,将x264库其编译进去。android-build-ffmpeg.sh是原来的编译脚本,在原来的编译脚本./configure增加如下选项。

# 以下是编译x264库增加的
# 禁用所有编码器
--disable-encoders \
# 通过libx264库启用H.264编码
--enable-libx264 \
# 启用编码器名称
--enable-encoder=libx264 \
# 启用几个图片编码,由于生成视频预览
--enable-encoder=mjpeg \
--enable-encoder=png \
#和FFmpeg动态库一起编译,指定你之前编译好的x264静态库和头文件
--extra-cflags="-I/Users/chenchangqing/Documents/code/ffmpeg/06_ffmpeg_video_encoding/android_build_x264/include" \
--extra-ldflags="-L/Users/chenchangqing/Documents/code/ffmpeg/06_ffmpeg_video_encoding/android_build_x264/lib" \

android-build-ffmpeg-x264.sh是修改后的脚本,再次编译FFmpeg库,重新生成.so动态库。

重新编译,发现错误:

libavcodec/libx264.c: In function 'X264_frame':
libavcodec/libx264.c:282:9: error: 'x264_bit_depth' undeclared (first use in this function)
     if (x264_bit_depth > 8)
         ^
libavcodec/libx264.c:282:9: note: each undeclared identifier is reported only once for each function it appears in
libavcodec/libx264.c: In function 'X264_init_static':
libavcodec/libx264.c:892:9: error: 'x264_bit_depth' undeclared (first use in this function)
     if (x264_bit_depth == 8)
         ^
make: *** [libavcodec/libx264.o] Error 1

查询资料(“x264_bit_depth”未声明),是因为ffmpeg和x264不兼容,这里不使用最新版本的x264,尝试另一个版本的x264,重新编译,再重新生成.so动态库。

再次运行测试工程,成功输出:

I/main: 编码器名称为:libx264

问题解决。

3. 解决问题(iOS)

这个问题在iOS上也是存在的,这里也列出解决步骤。

(1) 下载源码

x264库,翻墙更快。

git clone https://code.videolan.org/videolan/x264.git
(2) 编译x264脚本

ios-build-x264.sh是编译脚本,将编译脚本放在和源码的同一目录,执行:

sh ios-build-x264.sh

注意:如果使用旧版本的x264,比如这个x264,会出现下面的问题,所以我这里使用的当时最新的x264。

Out of tree builds are impossible with config.h/x264_config.h in source dir.
(3) 编译FFmpeg

修改iOS的FFmpeg库编译脚本,将x264库其编译进去。ios-build-ffmpeg.sh是原来的编译脚本,在原来的编译脚本./configure增加如下选项。

# 以下是编译x264库增加的
--enable-gpl \
--disable-encoders \
--enable-libx264 \
--enable-encoder=libx264 \
--enable-encoder=mjpeg \
--enable-encoder=png \
--extra-cflags="-I/Users/yangshaohong/Desktop/ffmpeg-test/test/thin-x264/arm64/include" \
--extra-ldflags="-L/Users/yangshaohong/Desktop/ffmpeg-test/test/thin-x264/arm64/lib" \

ios-build-ffmpeg-x264.sh是修改后的脚本,再次编译FFmpeg库,重新生成.a静态库。

用了最新的x264,还是出现了问题:

src/libavcodec/libx264.c:282:9: error: use of undeclared identifier 'x264_bit_depth'
    if (x264_bit_depth > 8)
        ^
src/libavcodec/libx264.c:892:9: error: use of undeclared identifier 'x264_bit_depth'
    if (x264_bit_depth == 8)
        ^
src/libavcodec/libx264.c:894:14: error: use of undeclared identifier 'x264_bit_depth'
    else if (x264_bit_depth == 9)
             ^
src/libavcodec/libx264.c:896:14: error: use of undeclared identifier 'x264_bit_depth'
    else if (x264_bit_depth == 10)
             ^
4 errors generated.
make: *** [libavcodec/libx264.o] Error 1
make: *** Waiting for unfinished jobs....

查询资料(“x264_bit_depth”未声明),是因为ffmpeg和x264不兼容,这里不使用最新版本的x264,尝试另一个版本的x264-snapshot-20180730-2245-stable.tar.bz2,重新编译,成功生成了支持h264编码的FFmpeg静态库。

注意:这里x264和ffmpeg都指定了arm64的架构。

1.1.7. 第七步:打开视频编码器

注意:代码中的“优化步骤”是必须的,要不然编码过程会有坑。

// 第七步:打开视频编码器
// 以下是编码优化步骤,必须有,要不然编码会出问题
// 编码延时问题,编码选项->编码设置
AVDictionary *param = 0;
if (avcodec_context->codec_id == AV_CODEC_ID_H264) {
    // 需要查看x264源码->x264.c文件
    // 第一个值:预备参数
    // key: preset
    // value: slow->慢
    // value: superfast->超快
    av_dict_set(&param, "preset", "slow", 0);
    // 第二个值:调优
    // key: tune->调优
    // value: zerolatency->零延迟
    av_dict_set(&param, "tune", "zerolatency", 0);
}
// 打开编码器
if (avcodec_open2(avcodec_context, avcodec, &param) < 0) {
    __android_log_print(ANDROID_LOG_INFO, "main", "打开编码器失败");
    // iOS使用
    // NSLog(@"打开编码器失败");
    return;
}

1.1.8. 第八步:写入文件头信息

// 第八步:写入文件头信息
avformat_write_header(avformat_context, NULL);

1.1.9. 第九步:打开yuv文件

// 第九步:打开yuv文件
// 遇到问题:fopen Permission denied
const char *cinFilePath = env->GetStringUTFChars(in_file_path, NULL);
// iOS使用
// const char *cinFilePath = [inFilePath UTF8String];
int errNum = 0;
FILE *in_file = fopen(cinFilePath, "rb");
if (in_file == NULL) {
    errNum = errno;
    __android_log_print(ANDROID_LOG_INFO, "main", "文件不存在:%s,in_file:%s,errNum:%d,reason:%s", cinFilePath, in_file, errNum, strerror(errNum));
    // iOS使用
    // NSLog(@"文件不存在");
    return;
}

这一步有坑,打开yuv文件(fopen)一直出现“Permission denied”错误,困扰了有一天,最后还是没有找到很好的办法,但是有个临时解决办法,就是先执行视频解码为.yuv文件,这个时候去打开(fopen)刚生成的.yuv文件,是可以成功的。

1.1.10. 第十步:视频编码准备

// 第十步:视频编码准备
// 10.1 创建视频原始数据帧
// 作用:存储视频原始数据帧
AVFrame *av_frame = av_frame_alloc();

// 10.2 创建一个缓冲区
// 作用:用于缓存读取视频数据
// 先获取缓冲区大小
int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,
                                           avcodec_context->width,
                                           avcodec_context->height,
                                           1);
// 创建一个缓冲区,作用是缓存一帧视频像素数据
uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);

// 10.3 填充视频原始数据帧
av_image_fill_arrays(av_frame->data,
                     av_frame->linesize,
                     out_buffer,
                     avcodec_context->pix_fmt,
                     avcodec_context->width,
                     avcodec_context->height,
                     1);

// 10.4 创建压缩数据帧数据
// 作用:接收压缩数据帧数据
AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);

1.1.11. 第十一步:循环读取视频像素数据

视频编码读取视频像素数据问题分析?

答案如下:

比例规范:y : u : v = 4 : 1 : 1
然后规范:y = width(视频宽)* height(高)
假设:width = 100,height = 10
所以:y = width * height = 1000
所以:u = y / 4 = 1000 / 4 = 250,v = y / 4 = 1000 / 4 = 250
也就是说:一帧yuv大小 = 1500
编码的时候读取一帧数据:fread(out_buffer, 1, y_size * 3 / 2, in_file)
y_size * 3 / 2 = 1000 * 3 / 2 = 1500
代码:av_frame->data[0] = out_buffer
解释:指针是从out_buffer = 0开始,所以data[0]读取范围:0-1000
代码:av_frame->data[1] = out_buffer + y_size
解释:指针是从out_buffer + y_size = 0 + 1000 = 1000开始,所以data[1]读取范围:1000-1250
代码:av_frame->data[2] = out_buffer + y_size * 5 / 4
解释:指针是从out_buffer + y_size * 5 / 4 = 0 + 1000 * 5 / 4 = 1250开始,所以data[2]读取范围:1250-1500

一帧数据->大小 = Y大小 + U大小 + V大小
假设:width = 100,height = 10
    Y大小:y = width * height = 100 * 10 = 1000
    U大小:u = y / 4 = 1000 / 4 = 250
    V大小:v = y / 4 = 1000 / 4 = 250
    一帧数据大小 = Y + U + V = 1500
视频解码计算->指针位移处理
    保存Y大小:
        fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
        avframe_yuv420p->data[0]->表示Y值
        读取:0->1000
    保存U大小
        fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
        avframe_yuv420p->data[1]->表示U值
        读取:1000->1250
    保存V大小
        fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
        avframe_yuv420p->data[2]->表示V值
        读取:1250->1500
视频编码计算->指针位移计算
    分析读取数据大小?
        y = 1000
        数据大小 = 一帧YUV数据 = Y + U + V = 1500
        数据大小 = y * 3 / 2 = 1000 * 3 / 2 = 1500
        现在我们视频编码根据Y大小,求出YUV大小计算公式
        out_buffer = 1500(总的数据量)
    保存Y大小
        av_frame->data[0] = out_buffer;
        读取Y数据->1000
        读取:0->1000
    保存U大小
        av_frame->data[1] = out_buffer + y_size;
        读取U数据->250
        读取:0 + 1000 -> 1250
    保存V大小
        av_frame->data[2] = out_buffer + y_size * 5 / 4;
        读取V数据->250
        读取:0 + 1000 * 5 / 4 = 1250->1500
    说白了:通过Y值得到V读取起点位置
// 第十一步:循环读取视频像素数据
// 编码是否成功
int result = 0;
int current_frame_index = 1;
int i = 0;
// 计算y的大小
int y_size = avcodec_context->width * avcodec_context->height;
while (true) {
    // 从yuv文件里面读取缓冲区
    // 读取大小:y_size * 3 / 2
    if (fread(out_buffer, 1, y_size * 3 / 2, in_file) <= 0) {
        __android_log_print(ANDROID_LOG_INFO, "main", "读取完毕...");
        // iOS使用
        // NSLog(@"读取完毕...");
        break;
    } else if (feof(in_file)) {
        break;
    }

    // 将缓冲区数据转成AVFrame类型
    // Y值
    av_frame->data[0] = out_buffer;
    // U值
    av_frame->data[1] = out_buffer + y_size;
    // V值
    av_frame->data[2] = out_buffer + y_size * 5 / 4;
    // 帧数
    av_frame->pts = i;
    // 注意时间戳
    i++;

    // 第十二步:视频编码处理
    // ...
    current_frame_index++;
}

1.1.12. 第十二步:视频编码处理

代码位置在第十一步。

// 第十二步:视频编码处理
// 发送一帧视频像素数据
avcodec_send_frame(avcodec_context, av_frame);
// 接收一帧视频像素数据,编码为视频压缩数据格式
result = avcodec_receive_packet(avcodec_context, av_packet);
// 判定是否编码成功
if (result == 0) {
    // 编码成功

    // 第十三步:将视频压缩数据写入到输出文件中
    // ...
} else {
    __android_log_print(ANDROID_LOG_INFO, "main", "编码第%d帧失败2", current_frame_index);
    // iOS使用
    // NSLog(@"编码第%d帧失败2", current_frame_index);
    return;
}

1.1.13. 第十三步:将视频压缩数据写入到输出文件中

代码位置在第十二步。

// 第十三步:将视频压缩数据写入到输出文件中
av_packet->stream_index = av_video_stream->index;
result = av_write_frame(avformat_context, av_packet);
current_frame_index++;
// 是否输出成功
if (result < 0) {
    __android_log_print(ANDROID_LOG_INFO, "main", "编码第%d帧失败", current_frame_index);
    // iOS使用
    // NSLog(@"编码第%d帧失败", current_frame_index);
    return;
} else {
    __android_log_print(ANDROID_LOG_INFO, "main", "编码第%d帧成功", current_frame_index);
    // iOS使用
    // NSLog(@"编码第%d帧成功", current_frame_index);
}

1.1.14. 第十四步:写入剩余帧数据

// 第十四步:写入剩余帧数据
// 作用:输出编码器中剩余AVPacket,可能没有
flush_encoder(avformat_context, 0);

1.1.15. 第十五步:写入文件尾部信息

// 第十五步:写入文件尾部信息
av_write_trailer(avformat_context);

1.1.16. 第十六步:释放内存,关闭编码器

// 第十六步:释放内存,关闭编码器
avcodec_close(avcodec_context);
av_free(av_frame);
av_free(out_buffer);
av_packet_free(&av_packet);
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
fclose(in_file);

1.2. 二、新建Android视频编码工程

1.2.1. 1. 新建工程

参考之前FFmpeg集成,新建ndk工程AndroidFFmpegEncodingVideo。

1.2.2. 2. 定义java方法

寻找MainActivity:app->src->main->java->MainActivity,增加代码如下:

public native void ffmpegVideoEncode(String inFilePath, String outFilePath);
public native void ffmepgDecodeVideo(String inFilePath, String outFilePath);

1.2.3. 3. 定义NDK方法

增加android打印。

#include <android/log.h>

在native-lib.cpp中,导入FFmpeg头文件。

extern "C" {
// 引入头文件
// 核心库->音视频编解码库
#include <libavcodec/avcodec.h>
// 封装格式处理库
#include "libavformat/avformat.h"
// 工具库
#include "libavutil/imgutils.h"
// 视频像素数据格式库
#include "libswscale/swscale.h"}

在native-lib.cpp中新增java方法flush_encoder、ffmepgVideoEncode、ffmepgDecodeVideo的C++实现,输入MainActivity.就会有代码提示,选择正确ffmepgVideoEncode方法补全代码。

ffmepgDecodeVideo方法实现参考FFmpeg视频解码

int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) {
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
          CODEC_CAP_DELAY))
        return 0;
    while (1) {
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                    NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame) {
            ret = 0;
            break;
        }
        __android_log_print(ANDROID_LOG_INFO, "main",
                            "Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
        /* mux encoded frame */
        ret = av_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_ccq_androidffmpegencodingvideo_MainActivity_ffmpegVideoEncode(JNIEnv *env, jobject thiz,
                                                                       jstring in_file_path,
                                                                       jstring out_file_path) {
    // 这里拷贝上面的视频编码流程的代码即可。
}

extern "C"
JNIEXPORT void JNICALL
Java_com_ccq_androidffmpegdecodingaudio_MainActivity_ffmepgDecodeVideo(JNIEnv *env, jobject thiz,
                                                                       jstring in_file_path,
                                                                       jstring out_file_path) {
}

1.3. 三、测试Android视频编码工程

准备视频文件:test.mov

在AndroidManifest.xml增加SD卡的读写权限。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

MainActivity增加测试代码,这里先进行视频解码,生成的.yuv文件后,直接对.yuv文件再进行编码。

注意:如果打开失败,可能读写存储设备的权限被禁用。

摩托罗拉·刀锋:设置->应用和通知->高级->权限管理器->隐私相关·读写存储设备->找到应用->如果禁用,则修改为允许。

import android.os.Environment;
import java.io.File;
import java.io.IOException;
import android.util.Log;

private void ffmpegVideoEncode() {
    String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
    String downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
    String inFilePath = downloadPath.concat("/test.yuv");
    String outFilePath = downloadPath.concat("/test.h264");

    // 文件不存在我创建一个文件
    File file = new File(outFilePath);
    if (file.exists()) {
        Log.i("日志:","存在");
    } else {
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    ffmpegVideoEncode(inFilePath, outFilePath);
}

private void ffmepgDecodeVideo() {
    String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
    String downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
    String inFilePath = downloadPath.concat("/test.mov");
    String outFilePath = downloadPath.concat("/test.yuv");

    // 文件不存在我创建一个文件
    File file = new File(outFilePath);
    if (file.exists()){
        Log.i("日志:","存在");
    }else {
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    ffmepgDecodeVideo(inFilePath, outFilePath);
}

ffmepgDecodeVideo();
ffmpegVideoEncode();

run工程代码,正确打印,同时正确生成.h264文件。

I/日志:: 存在
I/main: 解码器名称:h264
I/main: 当前解码第1帧
.
.
.
I/main: 当前解码第600帧
I/日志:: 存在
I/main: 编码器名称为:libx264
I/main: 编码第1帧成功
.
.
.
I/main: 编码第598帧成功
读取完毕...

h264文件播放:

ffplay test.h264

1.4. 四、新建iOS视频编码工程

1.4.1. 1. 新建工程

参考之前FFmpeg集成,新建ndk工程iOSFFmpegEncodingVideo。

注意:工程使用的是支持h264编码的FFmpeg库文件。

1.4.2. 2. 导入资源文件

资源文件就是视频解码后的.yuv文件。先将.yuv文件拷贝至工程目录下,再通过add files的方式加入工程。

1.4.3. 3. 导入x264静态库

在工程目录新建x264,拷贝编译好的thin-x264文件夹至x264目录,只保留arm64的文件夹,删除lib文件夹中的pkgconfig,再通过add files的方式加入工程。

配置x264头文件,参考FFmpeg集成

1.4.4. 4. 增加视频编码方法

(1) 导入FFmpeg头文件

修改FFmpegTest.h,新增如下:

//核心库
#include "libavcodec/avcodec.h"
//封装格式处理库
#include "libavformat/avformat.h"
//工具库
#include "libavutil/imgutils.h"

(2) 新增视频编码方法

修改FFmpegTest.h,新增如下:

/// FFmpeg视频编码
+ (void)ffmpegVideoEncode:(NSString*)filePath outFilePath:(NSString*)outFilePath;

修改FFmpegTest.m,新增如下:

int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) {
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
          CODEC_CAP_DELAY))
        return 0;
    while (1) {
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                    NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame) {
            ret = 0;
            break;
        }
        NSLog(@"Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
        /* mux encoded frame */
        ret = av_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}

+ (void)ffmpegVideoEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath {
    // 代码复制视频编码流程中的代码
    // 将备注`iOS使用`的代码打开
}

(3) 增加方法测试

修改ViewController.m,新增测试代码如下:

NSString* inPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"yuv"];
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

                                                     NSUserDomainMask, YES);
NSString* path = [paths objectAtIndex:0];
NSString* tmpPath = [path stringByAppendingPathComponent:@"temp"];
[[NSFileManager defaultManager] createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:NULL];
NSString* outFilePath = [tmpPath stringByAppendingPathComponent:[NSString stringWithFormat:@"test.h264"]];
[FFmpegTest ffmpegVideoEncode:inPath outFilePath:outFilePath];

run工程代码,正确打印,同时正确生成.h264文件。

iOSFFmpegEncodingVideo[828:210395] 编码器名称为:libx264
[libx264 @ 0x107021e00] using cpu capabilities: ARMv8 NEON
[libx264 @ 0x107021e00] profile High, level 3.0
[h264 @ 0x10701c200] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
[h264 @ 0x10701c200] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
[libx264 @ 0x107021e00] AVFrame.format is not set
[libx264 @ 0x107021e00] AVFrame.width or height is not set
2022-04-23 00:16:34.170713+0800 iOSFFmpegEncodingVideo[828:210395] 编码第1帧成功
[libx264 @ 0x107021e00] AVFrame.format is not set
[libx264 @ 0x107021e00] AVFrame.width or height is not set
2022-04-23 00:16:34.175975+0800 iOSFFmpegEncodingVideo[828:210395] 编码第2帧成功
[libx264 @ 0x107021e00] AVFrame.format is not set
[libx264 @ 0x107021e00] AVFrame.width or height is not set
.
.
.
2022-04-23 00:16:39.831069+0800 iOSFFmpegEncodingVideo[828:210395] 编码第598帧成功
2022-04-23 00:16:39.831365+0800 iOSFFmpegEncodingVideo[828:210395] 读取完毕...
[libx264 @ 0x107021e00] frame I:3     Avg QP:24.64  size: 20162
[libx264 @ 0x107021e00] frame P:595   Avg QP:25.38  size:  2245
[libx264 @ 0x107021e00] mb I  I16..4: 13.5% 50.9% 35.6%
[libx264 @ 0x107021e00] mb P  I16..4:  0.3%  0.5%  0.4%  P16..4: 40.8% 12.2%  4.0%  0.0%  0.0%    skip:41.7%
[libx264 @ 0x107021e00] final ratefactor: 23.64
[libx264 @ 0x107021e00] 8x8 transform intra:43.8% inter:54.5%
[libx264 @ 0x107021e00] coded y,uvDC,uvAC intra: 64.8% 75.5% 30.8% inter: 9.9% 13.2% 0.3%
[libx264 @ 0x107021e00] i16 v,h,dc,p: 21% 21%  7% 52%
[libx264 @ 0x107021e00] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 15% 13%  8%  8% 10% 11% 11% 10% 14%
[libx264 @ 0x107021e00] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 17% 15%  8%  9% 10% 11% 11%  8% 11%
[libx264 @ 0x107021e00] i8c dc,h,v,p: 46% 21% 24% 10%
[libx264 @ 0x107021e00] Weighted P-Frames: Y:0.3% UV:0.2%
[libx264 @ 0x107021e00] kb/s:466.92

results matching ""

    No results matching ""