From 39808aca1cdbfc89d585c98ad3d0dec12ca442d2 Mon Sep 17 00:00:00 2001
From: frank <839789740@qq.com>
Date: Fri, 2 Feb 2018 00:24:34 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=A7=A3=E7=A0=81=E6=92=AD?=
=?UTF-8?q?=E6=94=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
使用FFmpeg进行视频解码播放
---
README.md | 5 +-
app/CMakeLists.txt | 3 +-
app/src/main/AndroidManifest.xml | 4 +
app/src/main/cpp/video_player.c | 161 ++++++++++++++++++
.../java/com/frank/ffmpeg/VideoPlayer.java | 16 ++
.../ffmpeg/activity/VideoHandleActivity.java | 8 +
.../ffmpeg/activity/VideoPlayerActivity.java | 101 +++++++++++
.../main/res/layout/activity_video_handle.xml | 9 +
.../main/res/layout/activity_video_player.xml | 31 ++++
app/src/main/res/values/strings.xml | 3 +
10 files changed, 339 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/cpp/video_player.c
create mode 100644 app/src/main/java/com/frank/ffmpeg/VideoPlayer.java
create mode 100644 app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java
create mode 100644 app/src/main/res/layout/activity_video_player.xml
diff --git a/README.md b/README.md
index d34d87e..34b7d79 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ android端基于FFmpeg库在中的使用。
- #### 音频转码
- #### 音视频合成
- #### 音频抽取
+- #### 音频解码播放
- #### 视频抽取
- #### 视频剪切
- #### 视频转码
@@ -15,8 +16,10 @@ android端基于FFmpeg库在中的使用。
- #### 视频转GIF动图
- #### 视频添加水印
- #### 图片合成视频
+- #### 视频解码播放
+
***
-后续会加上音视频编解码。
+后续会加上音视频解码同步播放、直播推流。
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index f6106a9..3690aac 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -23,7 +23,8 @@ add_library( # Sets the name of the library.
src/main/cpp/ffmpeg_filter.c
src/main/cpp/ffmpeg_opt.c
src/main/cpp/audio_player.c
- src/main/cpp/openSL_audio_player.c)
+ src/main/cpp/openSL_audio_player.c
+ src/main/cpp/video_player.c)
add_library( ffmpeg
SHARED
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 93f494a..d34302a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/cpp/video_player.c b/app/src/main/cpp/video_player.c
new file mode 100644
index 0000000..1f20bc5
--- /dev/null
+++ b/app/src/main/cpp/video_player.c
@@ -0,0 +1,161 @@
+//
+// Created by frank on 2018/2/1.
+//
+#include "libavcodec/avcodec.h"
+#include "libavformat/avformat.h"
+#include "libswscale/swscale.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define TAG "MediaPlayer"
+#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, TAG, FORMAT, ##__VA_ARGS__);
+#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, TAG, FORMAT, ##__VA_ARGS__);
+
+//播放倍率
+float play_rate = 1;
+//视频总时长
+long duration = 0;
+
+JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_VideoPlayer_play
+ (JNIEnv * env, jclass clazz, jstring filePath, jobject surface){
+
+ const char * file_name = (*env)->GetStringUTFChars(env, filePath, JNI_FALSE);
+ //注册所有组件
+ av_register_all();
+ //分配上下文
+ AVFormatContext * pFormatCtx = avformat_alloc_context();
+ //打开视频文件
+ if(avformat_open_input(&pFormatCtx, file_name, NULL, NULL)!=0) {
+ LOGE("Couldn't open file:%s\n", file_name);
+ return -1;
+ }
+ //检索多媒体流信息
+ if(avformat_find_stream_info(pFormatCtx, NULL)<0) {
+ LOGE("Couldn't find stream information.");
+ return -1;
+ }
+ //寻找视频流的第一帧
+ int videoStream = -1, i;
+ for (i = 0; i < pFormatCtx->nb_streams; i++) {
+ if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
+ && videoStream < 0) {
+ videoStream = i;
+ }
+ }
+ if(videoStream==-1) {
+ LOGE("couldn't find a video stream.");
+ return -1;
+ }
+
+ //获取视频总时长
+ if (pFormatCtx->duration != AV_NOPTS_VALUE) {
+ duration = (long) (pFormatCtx->duration / AV_TIME_BASE);
+ LOGE("duration=%d", duration);
+ }
+
+ //获取codec上下文指针
+ AVCodecContext * pCodecCtx = pFormatCtx->streams[videoStream]->codec;
+ //寻找视频流的解码器
+ AVCodec * pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+ if(pCodec==NULL) {
+ LOGE("couldn't find Codec.");
+ return -1;
+ }
+ if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
+ LOGE("Couldn't open codec.");
+ return -1;
+ }
+ // 获取native window
+ ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env, surface);
+ // 获取视频宽高
+ int videoWidth = pCodecCtx->width;
+ int videoHeight = pCodecCtx->height;
+ // 设置native window的buffer大小,可自动拉伸
+ ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight, WINDOW_FORMAT_RGBA_8888);
+ ANativeWindow_Buffer windowBuffer;
+ if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) {
+ LOGE("Couldn't open codec.");
+ return -1;
+ }
+ //申请内存
+ AVFrame * pFrame = av_frame_alloc();
+ AVFrame * pFrameRGBA = av_frame_alloc();
+ if(pFrameRGBA == NULL || pFrame == NULL) {
+ LOGE("Couldn't allocate video frame.");
+ return -1;
+ }
+ // buffer中数据用于渲染,且格式为RGBA
+ int numBytes=av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height, 1);
+
+ uint8_t * buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
+ av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
+ pCodecCtx->width, pCodecCtx->height, 1);
+
+ // 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换
+ struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,
+ pCodecCtx->height,
+ pCodecCtx->pix_fmt,
+ pCodecCtx->width,
+ pCodecCtx->height,
+ AV_PIX_FMT_RGBA,
+ SWS_BILINEAR,
+ NULL,
+ NULL,
+ NULL);
+
+ int frameFinished;
+ AVPacket packet;
+
+ while(av_read_frame(pFormatCtx, &packet)>=0) {
+ //判断是否为视频流
+ if(packet.stream_index==videoStream) {
+ //对该帧进行解码
+ avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
+ if (frameFinished) {
+ // lock native window
+ ANativeWindow_lock(nativeWindow, &windowBuffer, 0);
+ // 格式转换
+ sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
+ pFrame->linesize, 0, pCodecCtx->height,
+ pFrameRGBA->data, pFrameRGBA->linesize);
+ // 获取stride
+ uint8_t * dst = windowBuffer.bits;
+ int dstStride = windowBuffer.stride * 4;
+ uint8_t * src = pFrameRGBA->data[0];
+ int srcStride = pFrameRGBA->linesize[0];
+ // 由于window的stride和帧的stride不同,因此需要逐行复制
+ int h;
+ for (h = 0; h < videoHeight; h++) {
+ memcpy(dst + h * dstStride, src + h * srcStride, (size_t) srcStride);
+ }
+ ANativeWindow_unlockAndPost(nativeWindow);
+ }
+ //延迟等待
+ usleep((unsigned long) (1000 * 40 * play_rate));
+ }
+ av_packet_unref(&packet);
+ }
+ //释放内存以及关闭文件
+ av_free(buffer);
+ av_free(pFrameRGBA);
+ av_free(pFrame);
+ avcodec_close(pCodecCtx);
+ avformat_close_input(&pFormatCtx);
+ return 0;
+}
+
+//设置播放速率
+JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_VideoPlayer_setPlayRate
+ (JNIEnv * env, jclass clazz, jfloat playRate){
+ play_rate = playRate;
+}
+
+//获取视频总时长
+JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_VideoPlayer_getDuration
+ (JNIEnv * env, jclass clazz){
+ return duration;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/frank/ffmpeg/VideoPlayer.java b/app/src/main/java/com/frank/ffmpeg/VideoPlayer.java
new file mode 100644
index 0000000..cd5f806
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/VideoPlayer.java
@@ -0,0 +1,16 @@
+package com.frank.ffmpeg;
+
+/**
+ * 视频播放器
+ * Created by frank on 2018/2/1
+ */
+public class VideoPlayer {
+
+ static {
+ System.loadLibrary("media-handle");
+ }
+
+ public native int play(String filePath, Object surface);
+ public native int setPlayRate(float playRate);
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
index f034c51..ac79c86 100644
--- a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
+++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
@@ -1,6 +1,7 @@
package com.frank.ffmpeg.activity;
import android.annotation.SuppressLint;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -65,6 +66,7 @@ public class VideoHandleActivity extends AppCompatActivity implements View.OnCli
findViewById(R.id.btn_generate_gif).setOnClickListener(this);
findViewById(R.id.btn_screen_record).setOnClickListener(this);
findViewById(R.id.btn_combine_video).setOnClickListener(this);
+ findViewById(R.id.btn_play_video).setOnClickListener(this);
}
private void setVisible() {
@@ -117,6 +119,9 @@ public class VideoHandleActivity extends AppCompatActivity implements View.OnCli
case R.id.btn_combine_video:
handleType = 7;
break;
+ case R.id.btn_play_video:
+ handleType = 8;
+ break;
default:
handleType = 0;
break;
@@ -196,6 +201,9 @@ public class VideoHandleActivity extends AppCompatActivity implements View.OnCli
String combineVideo = PATH + File.separator + "combineVideo.mp4";
commandLine = FFmpegUtil.pictureToVideo(picturePath, combineVideo);
break;
+ case 8:
+ startActivity(new Intent(VideoHandleActivity.this, VideoPlayerActivity.class));
+ return;
default:
break;
}
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java
new file mode 100644
index 0000000..8a0a41d
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java
@@ -0,0 +1,101 @@
+package com.frank.ffmpeg.activity;
+
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.Button;
+
+import com.frank.ffmpeg.R;
+import com.frank.ffmpeg.VideoPlayer;
+
+import java.io.File;
+
+/**
+ * 使用ffmpeg播放视频
+ * Created by frank on 2018/2/1.
+ */
+public class VideoPlayerActivity extends AppCompatActivity implements SurfaceHolder.Callback {
+ private static final String TAG = MainActivity.class.getSimpleName();
+ SurfaceHolder surfaceHolder;
+ private final static String PATH = Environment.getExternalStorageDirectory().getPath() + File.separator;
+ private String filePath = PATH + "hello.mp4";
+ private VideoPlayer videoPlayer;
+ //播放倍率
+ private float playRate = 1;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_video_player);
+
+ initView();
+ initPlayer();
+ }
+
+ private void initView(){
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_view);
+ surfaceHolder = surfaceView.getHolder();
+ surfaceHolder.addCallback(this);
+
+ Button btn_slow = (Button) findViewById(R.id.btn_slow);
+ btn_slow.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(playRate <= 32){
+ playRate *= 2;
+ }
+ Log.i(TAG, "playRate=" + playRate);
+ videoPlayer.setPlayRate(playRate);
+ }
+ });
+
+ Button btn_fast = (Button) findViewById(R.id.btn_fast);
+ btn_fast.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(playRate >= 1/32){
+ playRate *= 0.5;
+ }
+ Log.i(TAG, "playRate=" + playRate);
+ videoPlayer.setPlayRate(playRate);
+ }
+ });
+
+ }
+
+ private void initPlayer(){
+ videoPlayer = new VideoPlayer();
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ videoPlayer.play(filePath, surfaceHolder.getSurface());
+ }
+ }).start();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if(videoPlayer != null){
+ videoPlayer = null;
+ }
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_video_handle.xml b/app/src/main/res/layout/activity_video_handle.xml
index c32a039..d6b966e 100644
--- a/app/src/main/res/layout/activity_video_handle.xml
+++ b/app/src/main/res/layout/activity_video_handle.xml
@@ -85,4 +85,13 @@
android:layout_centerInParent="true"
android:visibility="gone"/>
+
+
diff --git a/app/src/main/res/layout/activity_video_player.xml b/app/src/main/res/layout/activity_video_player.xml
new file mode 100644
index 0000000..27cb68e
--- /dev/null
+++ b/app/src/main/res/layout/activity_video_player.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9b8de78..1960755 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -25,5 +25,8 @@
图片合成视频
视频抽帧
视频局部放大
+ 视频播放
+ 慢放
+ 快进