音视频实践学习
- android全平台编译ffmpeg以及x264与fdk-aac实践
- ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器
- android全平台编译ffmpeg合并为单个库实践
- android-studio使用cmake编译ffmpeg实践
- android全平台下基于ffmpeg解码MP4视频文件为YUV文件
- android全平台编译ffmpeg支持命令行实践
- android全平台编译ffmpeg视频推流实践
- android平台下音频编码之编译LAME库转码PCM为MP3
- ubuntu平台下编译vlc-android视频播放器实践
- 图解YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P、YUV444P的区别
- 图解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的区别
- YUV420P、YUV420SP、NV12、NV21和RGB互相转换并存储为JPEG以及PNG图片
- android全平台编译libyuv库实现YUV和RGB的转换
概述
在音视频开发中几乎都要涉及两个非常重要的环节:编码和解码
,今天要记录的就是其中的解码
环节,将我们的目标mp4文件
,解码成yuv文件输出
流程
关键函数说明
//注册FFmpeg所有编解码器。
av_register_all()
//创建AVFormatContext结构体。
avformat_alloc_context()
//打开一个输入流。
avformat_open_input()
//获取媒体的信息。
avformat_find_stream_info()
//查找解码器。
avcodec_find_decoder()
//配置解码器。
avcodec_alloc_context3()
//打开解码器。
avcodec_open2()
//发送AVPacket数据给解码器
avcodec_send_packet()
//获取AVFrame
avcodec_receive_frame()
配置环境
操作系统:ubuntu 16.05
ffmpeg版本:ffmpeg-3.3.8
注意: ffmpeg库
的编译使用的是android-ndk-r10e版本
,使用高版本编译会报错
而android-studio
工程中配合cmake
使用的版本则是android-ndk-r16b版本
新建工程ffmpeg-single-decode
配置CMakeLists.txt文件和build.gradle文件
比较简单,不再赘述。
新建类NativeDecode类
package com.onzhou.ffmpeg.decode;
public class NativeDecode {
static {
System.loadLibrary("native-decode");
}
public native void decodeMP4(String mp4Path, String yuvPath);
}
为了方便后续的扩展,笔者这里新建了一个简单的解码器基类
:
class VideoDecoder {
public:
virtual int InitDecoder(const char *videoPath) = 0;
virtual int DecodeFile(const char *yuvPath) = 0;
};
具体的MP4Decoder解码器实现类
#include <stdio.h>
#include <time.h>
#include "decode_mp4.h"
#include "logger.h"
int MP4Decoder::InitDecoder(const char *mp4Path) {
// 1.注册所有组件
av_register_all();
// 2.创建AVFormatContext结构体
pFormatCtx = avformat_alloc_context();
// 3.打开一个输入文件
if (avformat_open_input(&pFormatCtx, mp4Path, NULL, NULL) != 0) {
LOGE("could not open input stream");
return -1;
}
// 4.获取媒体的信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
LOGE("could not find stream information");
return -1;
}
//获取视频轨的下标
int videoIndex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
if (videoIndex == -1) {
LOGE("could not find a video stream");
return -1;
}
// 5.查找解码器
pCodec = avcodec_find_decoder(pFormatCtx->streams[videoIndex]->codecpar->codec_id);
if (pCodec == NULL) {
LOGE("could not find Codec");
return -1;
}
// 6.配置解码器
pCodecCtx = avcodec_alloc_context3(pCodec);
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoIndex]->codecpar);
pCodecCtx->thread_count = 1;
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
LOGE("could not open codec");
return -1;
}
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height, 1);
uint8_t *out_buffer = (unsigned char *) av_malloc(bufferSize);
av_image_fill_arrays(pFrameYUV->data,
pFrameYUV->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height, 1);
pAvPacket = (AVPacket *) av_malloc(sizeof(AVPacket));
pSwsContext = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
return 0;
}
/**
* 解码
* @param pCodecCtx
* @param pAvPacket
* @param pFrame
* @return
*/
int MP4Decoder::DecodePacket(AVCodecContext *pCodecCtx, AVPacket *pAvPacket, AVFrame *pFrame) {
int result = avcodec_send_packet(pCodecCtx, pAvPacket);
if (result < 0) {
LOGE("send packet for decoding failed");
return -1;
}
while (!result) {
result = avcodec_receive_frame(pCodecCtx, pFrame);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
return 0;
} else if (result < 0) {
LOGE("error during encoding %d", result);
return -1;
}
sws_scale(pSwsContext,
(const uint8_t *const *) pFrame->data,
pFrame->linesize,
0,
pCodecCtx->height,
pFrameYUV->data,
pFrameYUV->linesize);
int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, y_size, yuv_file); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, yuv_file); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, yuv_file); //V
av_frame_unref(pFrame);
}
return 0;
}
/**
* 解码文件
* @param yuvPath 目标的yuv文件路径
* @return
*/
int MP4Decoder::DecodeFile(const char *yuvPath) {
yuv_file = fopen(yuvPath, "wb+");
if (yuv_file == NULL) {
LOGE("could not open output file");
return -1;
}
while (av_read_frame(pFormatCtx, pAvPacket) >= 0) {
DecodePacket(pCodecCtx, pAvPacket, pFrame);
}
//收尾
DecodePacket(pCodecCtx, NULL, pFrame);
if (pSwsContext != NULL) {
sws_freeContext(pSwsContext);
pSwsContext = NULL;
}
//关闭文件
fclose(yuv_file);
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
pCodecCtx = NULL;
}
if (pFrame != NULL) {
av_free(pFrame);
pFrame = NULL;
}
if (pFrameYUV != NULL) {
av_free(pFrameYUV);
pFrameYUV = NULL;
}
if (pFormatCtx != NULL) {
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
pFormatCtx = NULL;
}
return 0;
}
编写native层的实现类:native_decode.cpp
#include <stdio.h>
#include <time.h>
#include "native_decode.h"
#include "decode_mp4.h"
/**
* 动态注册
*/
JNINativeMethod methods[] = {
{"decodeMP4", "(Ljava/lang/String;Ljava/lang/String;)V", (void *) decodeMP4}
};
/**
* 动态注册
* @param env
* @return
*/
jint registerNativeMethod(JNIEnv *env) {
jclass cl = env->FindClass("com/onzhou/ffmpeg/decode/NativeDecode");
if ((env->RegisterNatives(cl, methods, sizeof(methods) / sizeof(methods[0]))) < 0) {
return -1;
}
return 0;
}
/**
* 加载默认回调
* @param vm
* @param reserved
* @return
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//注册方法
if (registerNativeMethod(env) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
void decodeMP4(JNIEnv *env, jobject obj, jstring jmp4Path, jstring jyuvPath) {
VideoDecoder *mp4Decoder = new MP4Decoder();
const char *mp4Path = env->GetStringUTFChars(jmp4Path, NULL);
const char *yuvPath = env->GetStringUTFChars(jyuvPath, NULL);
mp4Decoder->InitDecoder(mp4Path);
mp4Decoder->DecodeFile(yuvPath);
env->ReleaseStringUTFChars(jmp4Path, mp4Path);
env->ReleaseStringUTFChars(jyuvPath, yuvPath);
delete mp4Decoder;
}
在我们的应用程序中,点击开始解码:
public void onDecodeClick(View view) {
if (fFmpegDecode == null) {
fFmpegDecode = new FFmpegDecode();
}
btnDecode.setEnabled(false);
final File fileDir = getExternalFilesDir(null);
Schedulers.newThread().scheduleDirect(new Runnable() {
@Override
public void run() {
fFmpegDecode.decodeMP4(fileDir.getAbsolutePath() + "/input.mp4", fileDir.getAbsolutePath() + "/output.yuv");
}
});
}
编译打包运行,输出如下信息:
输出的yuv文件
体积很大,我们将yuv文件
同步到电脑上。
ffplay -f rawvideo -video_size 480x960 output.yuv
项目地址:ffmpeg-single-decode
https://github.com/byhook/ffmpeg4android
参考雷神:
https://blog.csdn.net/leixiaohua1020/article/details/47010637
https://blog.csdn.net/leixiaohua1020/article/details/42181571