在安卓开发过程中,使用ffmpeg进行音视频的处理和编解码是比较常见的一种方式。ffmpeg 是一款自由、开源的跨平台多媒体框架,可以完成音视频的采集、编解码、过滤以及推拉流等功能。本篇文章将着重介绍ffmpeg在安卓开发中的原理和使用方法。
一、ffmpeg的基础知识
1. ffmpeg的组成
ffmpeg由以下三个组件组成:
- libavcodec:用于实现音视频的编解码。
- libavformat:用于实现音视频的封装和解封装。
- libavutil:提供了一些公共功能。
2. ffmpeg的常用格式
下面是ffmpeg支持的常见音视频格式:
音频编解码格式:MP3、AAC、WAV、FLAC、PCM、AMR、OGG、WMA等。
视频编解码格式:H.264、MPEG-4、VP8、Theora、WMV等。
音视频封装格式:AVI、MP4、FLV、MKV、MOV等。
3. ffmpeg的常用命令
下面是ffmpeg常用的一些命令:
- -i:指定输入文件
- -codec:指定编码器或解码器
- -f:指定输出格式
- -b:指定比特率
- -r:指定帧率
- -s:指定分辨率
二、ffmpeg在安卓开发中的使用
1. 集成ffmpeg库
首先,我们需要将ffmpeg库集成到我们的安卓项目中。一般情况下,我们可以直接下载ffmpeg的预编译库,将so文件拷贝到安卓项目的libs目录下,然后在gradle配置文件中添加以下代码:
```groovy
android {
//...
defaultConfig {
//...
// 加载.so库
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
//...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
```
2. 使用ffmpeg库
在我们集成完成ffmpeg库之后,就可以在我们的安卓项目中使用ffmpeg了。
首先,我们需要在Java代码中,引入ffmpeg库的头文件:
```java
package com.example.ffmpegdemo;
public class FFmpegUtil {
static {
System.loadLibrary("avutil");
System.loadLibrary("swresample");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("swscale");
System.loadLibrary("avfilter");
System.loadLibrary("ffmpeg");
}
//...
}
```
然后,我们就可以在Java代码中使用ffmpeg库提供的API。例如,我们要对一个MP4格式的视频进行解码:
```java
public class FFmpegUtil {
//...
/**
* 视频解码
* @param inputPath 输入路径
* @param outputPath 输出路径
* @return 0-成功,其他-失败
*/
public native int decodeVideo(String inputPath, String outputPath);
//...
}
```
在对应的C++代码中,我们通过codec、format等模块来实现对MP4视频的解码:
```c++
#include
#include
#include
#include
#include
#include
#include "ffmpeg/include/libavutil/avutil.h"
#include "ffmpeg/include/libavcodec/avcodec.h"
#include "ffmpeg/include/libavformat/avformat.h"
#include "ffmpeg/include/libavfilter/avfilter.h"
#include "ffmpeg/include/libswresample/swresample.h"
#include "ffmpeg/include/libswscale/swscale.h"
#define LOG_TAG "jni_ffmpeg"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C" {
// 视频解码
JNIEXPORT jint JNICALL Java_com_example_ffmpegdemo_FFmpegUtil_decodeVideo(JNIEnv *env, jobject, jstring jInputPath, jstring jOutputPath) {
const char* inputPath = (env)->GetStringUTFChars(jInputPath, 0);
const char* outputPath = (env)->GetStringUTFChars(jOutputPath, 0);
AVFormatContext* inputFormatCxt = nullptr;
AVCodecContext* codecCxt = nullptr;
AVCodecParameters* codecPara = nullptr;
AVCodec* codec = nullptr;
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
av_register_all();
avformat_network_init();
inputFormatCxt = avformat_alloc_context();
if (avformat_open_input(&inputFormatCxt, inputPath, nullptr, nullptr) < 0) {
LOGE("open input failed");
return -1;
}
if (avformat_find_stream_info(inputFormatCxt, nullptr) < 0) {
LOGE("find stream info failed")
return -1;
}
int videoIndex = -1;
for (int i = 0; i < inputFormatCxt->nb_streams; i++) {
if (inputFormatCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}
if (videoIndex == -1) {
LOGE("can not find video stream");
return -1;
}
codecPara = inputFormatCxt->streams[videoIndex]->codecpar;
codec = avcodec_find_decoder(codecPara->codec_id);
codecCxt = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCxt, codecPara);
if (avcodec_open2(codecCxt, codec, nullptr) < 0) {
LOGE("can not open codec");
return -1;
}
AVFormatContext* outputFormatCxt = nullptr;
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVStream* stream = avformat_new_stream(outputFormatCxt, codec);
codecCxt = stream->codec;
avcodec_parameters_from_context(stream->codecpar, codecCxt);
if (!(outputFormatCxt->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatCxt->pb, outputPath, AVIO_FLAG_WRITE) < 0) {
LOGE("can not open output file");
return -1;
}
}
if (avformat_write_header(outputFormatCxt, nullptr) < 0) {
LOGE("write output header failed");
return -1;
}
int ret = 0;
while (1) {
ret = av_read_frame(inputFormatCxt, packet);
if (ret < 0) {
LOGE("read frame failed %d", ret);
break;
}
if (packet->stream_index == videoIndex) {
ret = avcodec_send_packet(codecCxt, packet);
if (ret < 0) {
LOGE("send packet failed %d", ret);
break;
}
while (1) {
ret = avcodec_receive_frame(codecCxt, frame);
if (ret < 0) {
break;
}
//处理解码后的图像数据
//将图像数据编码成为H.264格式
//写入H.264格式数据到输出文件中
}
}
av_packet_unref(packet);
}
avcodec_close(codecCxt);
avcodec_free_context(&codecCxt);
avformat_close_input(&inputFormatCxt);
av_frame_free(&frame);
av_packet_free(&packet);
return 0;
}
}
```
以上是一个简单的音视频解码的示例,实现了将MP4文件解码成为原始的YUV格式的数据流,并通过一定的方式编码成为H.264格式的数据流并写入到输出文件中。
总结
ffmpeg在安卓开发中的使用方法和原理大致介绍完毕。在实际开发过程中,我们可以根据自己的需求,结合ffmpeg提供的各种API进行开发。同时,在使用过程中,需要注意ffmpeg库的引用、NDK开发支持等方面的问题。