曾经的痛,之前搞Android的时候很多东西都没有做总结,很多也忘了,慢慢重新整理回来吧,下面是实现安卓视频开发的部分关键代码,主要是用于存档,思路总结于注释,可参考(如有错误,欢迎指正 !)
CMake配置环境项目,gradle代码块:
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cn.itcast.newproject"
minSdkVersion 17
targetSdkVersion 28
externalNativeBuild{
cmake{
cppFlags ""
abiFilters "armeabi-v7a"
}
}
ndk{
abiFilters("armeabi-v7a")
}
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
手写FFmpeg && rtmp(导入别人的也行):  CMakeLists.txt:
cmake_minimum_required(VERSION 3.6.4111459)
set(FFMPEG ${CMAKE_SOURCE_DIR}/ffmpeg) ##拿到ffmpeg的路径
set(RTMP ${CMAKE_SOURCE_DIR}/rtmp) ##拿到rtmp的路径
include_directories(${FFMPEG}/include) ##导入ffmpeg的头文件
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${FFMPEG}/libs/${CMAKE_ANDROID_ARCH_ABI}") ##导入ffmpeg库指定
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${RTMP}/libs/${CMAKE_ANDROID_ARCH_ABI}") # rtmp库指定
##批量导入 源文件
file(GLOB src_files *.cpp)
add_library(
native-lib # 总库libnative-lib.so
SHARED # 动态库
${src_files})
target_link_libraries(
native-lib # 总库libnative-lib.so
##忽略顺序的方式,导入
-Wl,--start-group
avcodec avfilter avformat avutil swresample swscale
-Wl,--end-group
log # 日志库,打印日志用的
z # libz.so库,是FFmpeg需要用ndk的z库,FFMpeg需要额外支持 libz.so
rtmp # rtmp 后面会专门介绍
android # android 后面会专门介绍,目前先要明白的是 ANativeWindow 用来渲染画面的
OpenSLES # OpenSLES 后面会专门介绍,目前先要明白的是 OpenSLES 用来播放声音的
)
熟悉一下之前的编码流程
项目流程图:

ffmpeg解封装解码流程API概况:

activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="200dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_margin="5dp">
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="00:00/00:00"
android:visibility="gone" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:max="100"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
搭建C++上层:
思路在注释上
package cn.itcast.newproject;
public class PlayerClass {
static {
System.loadLibrary("native-lib");
}
private OnPreparedListener onPreparedListener;
public PlayerClass() {
}
private String dataSource;
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public void prepare() {
prepareNative(dataSource);
}
public void start() {
startNative(); }
public void stop() {
stopNative(); }
public void release() {
releaseNative(); }
public void onPrepared() {
if (onPreparedListener != null) {
onPreparedListener.onPrepared();
}
}
public void setOnPreparedListener(OnPreparedListener onPreparedListener) {
this.onPreparedListener = onPreparedListener; }
public interface OnPreparedListener {
void onPrepared();}
private native void prepareNative(String dataSource);
private native void startNative();
private native void stopNative();
private native void releaseNative();
}
Java层MainActivity(上层):
package cn.itcast.newproject;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Environment;
import android.view.WindowManager;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private PlayerClass player;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_main);
player = new PlayerClass();
player.setDataSource(
new File(Environment.getExternalStorageDirectory() + File.separator + "demo.mp4")
.getAbsolutePath());
player.setOnPreparedListener(new PlayerClass.OnPreparedListener() {
@Override
public void onPrepared() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "准备完成,即将开始播放", Toast.LENGTH_SHORT).show();
}
});
player.start();
}
});
}
@Override
protected void onResume() {
super.onResume();
player.prepare();
}
@Override
protected void onStop() {
super.onStop();
player.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
player.release();
}
}
完成Native函数实现(JNI函数):
#include <jni.h>
#include <string>
#include "DerryPlayer.h"
#include "JNICallbakcHelper.h"
extern "C"{
#include <libavutil/avutil.h>
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_derry_player_MainActivity_getFFmpegVersion(
JNIEnv *env,
jobject ) {
std::string info = "FFmpeg的版本号是:";
info.append(av_version_info());
return env->NewStringUTF(info.c_str());
}
DerryPlayer *player = 0;
JavaVM *vm = 0;
jint JNI_OnLoad(JavaVM * vm, void * args) {
::vm = vm;
return JNI_VERSION_1_6;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_player_DerryPlayer_prepareNative(JNIEnv *env, jobject job, jstring data_source) {
const char * data_source_ = env->GetStringUTFChars(data_source, 0);
auto *helper = new JNICallbakcHelper(vm, env, job);
player = new DerryPlayer(data_source_, helper);
player->prepare();
env->ReleaseStringUTFChars(data_source, data_source_);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_player_DerryPlayer_startNative(JNIEnv *env, jobject thiz) {
if (player) {
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_player_DerryPlayer_stopNative(JNIEnv *env, jobject thiz) {
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_player_DerryPlayer_releaseNative(JNIEnv *env, jobject thiz) {
}
C++实现文件:
#include "DerryPlayer.h"
DerryPlayer::PlayerClass(const char *data_source, JNICallbakcHelper *helper) {
this->data_source = new char[strlen(data_source) + 1];
strcpy(this->data_source, data_source);
this->helper = helper;
}
PlayerClass::~PlayerClass() {
if (data_source) {
delete data_source;
}
if (helper) {
delete helper;
}
}
void *task_prepare(void *args) {
auto *player = static_cast<PlayerClass *>(args);
player->prepare_();
return 0;
}
void PlayerClass::prepare_() {
formatContext = avformat_alloc_context();
AVDictionary *dictionary = 0;
av_dict_set(&dictionary, "timeout", "5000000", 0);
int r = avformat_open_input(&formatContext, data_source, 0, &dictionary);
av_dict_free(&dictionary);
if (r) {
return;
}
r = avformat_find_stream_info(formatContext, 0);
if (r < 0) {
return;
}
for (int i = 0; i < formatContext->nb_streams; ++i) {
AVStream *stream = formatContext->streams[i];
AVCodecParameters *parameters = stream->codecpar;
AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
AVCodecContext *codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {
return;
}
r = avcodec_parameters_to_context(codecContext, parameters);
if (r < 0) {
return;
}
r = avcodec_open2(codecContext, codec, 0);
if (r) {
return;
}
if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
audio_channel = new AudioChannel();
} else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
video_channel = new VideoChannel();
}
}
if (!audio_channel && !video_channel) {
return;
}
if (helper) {
helper->onPrepared(THREAD_CHILD);
}
}
void DerryPlayer::prepare() {
pthread_create(&pid_prepare, 0, task_prepare, this);
}
C++头文件:
#ifndef PLAYERCLASS_PLAYERCLASS_H
#define PLAYERCLASS_PLAYERCLASS_H
#include <cstring>
#include <pthread.h>
#include "AudioChannel.h"
#include "VideoChannel.h"
#include "JNICallbakcHelper.h"
#include "util.h"
extern "C" {
#include <libavformat/avformat.h>
};
class DerryPlayer {
private:
char *data_source = 0;
pthread_t pid_prepare;
AVFormatContext *formatContext = 0;
AudioChannel *audio_channel = 0;
VideoChannel *video_channel = 0;
JNICallbakcHelper *helper = 0;
public:
PlayerClass(const char *data_source, JNICallbakcHelper *helper);
~PlayerClass();
void prepare();
void prepare_();
};
#endif
For Alizary 《后续再补充》 《原本漫长的一天却已日暮西山》 《夕阳无限好,却是近黄昏,明天会更好》 
|