代码github地址https://github.com/ccj659/NDK-FFmpeg-master
AVFormatContext 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装
格式相关信息。
AVInputFormat 每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。
AVStream 视频文件中每个视频(音频)流对应一个该结构体。
AVCodecContext编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
AVPacket 存储一帧压缩编码数据。
AVFrame存储一帧解码后像素(采样)数据。
在本例中 以视频解码播放为例,在下手之前,请搞懂 FFmpeg解码的数据结构中的命名空间相关的属性以及作用.
在linux中编译ffmpeg
1.编写shell脚本文件
2.配置configuration
3.给文件权限:chmod 777 android_build.sh
4.执行 ./android_build.sh
1.下载libyuv的库,大家克隆即可https://chromium.googlesource.com/external/libyuv
2.执行ndk-build(linux需要提前配置ndk环境)
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");
}
}
/*
* 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);
}
/**
* 万能解码器,播放视频器
* @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();
//选择 - input.mp4
- 音频
String[] videoArray = getResources().getStringArray(R.array.video_list);
ArrayAdapter adapter = new ArrayAdapter(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);
}
}
}
log信息
解码器播放效果
1.由于绘制过程在主线程,所以会出现ANR,后期再处理
2.本项目是学习的一次实践,后期会继续补充
目前市面上的音视频解码播放器,大多都是基于ffmpeg的代码的,例如腾讯影音,暴风影音等等.在此,感谢jason老师,致敬ffmpeg雷霄骅的博客.
路漫漫其修远兮,吾将上下而求索.
1.github地址https://github.com/ccj659/NDK-FFmpeg-master
2.CSDN博客http://blog.csdn.net/ccj659/
3.简书地址http://www.jianshu.com/users/94423b4ef5cf/latest_articles
4.QQ : 569948231