FFmpeg を使用して Android のビデオ録画と圧縮を再生する

この記事は Jianxi によって独占的に承認されています. Jianxi は Android マルチメディア開発も行っています. 彼はフェローです. しかし, 彼は主にビデオ圧縮に焦点を当てています. FFmpeg を使用すると、多くのことができますが、効果はあまりありません. 今日の彼の共有を見てください。Jianxi のブログは次のとおりです

1. ウォーミングアップ


時は過ぎ、時は過ぎ去り、最後の自慢から2、3か月が経ちました。私の周りの多くの女性が分裂して再会し、再会して分裂しました。このディックはまだ誇らしげに独身です。前回、簡単な FFmpeg コマンドと Java 層の簡単な呼び出し方法について大雑把に話しましたが、多くの友人が github や csdn にメッセージを残してくれました. ライブラリが以前は so パッケージを使用していたため、返信を避けることを選択しました.はオープンソースではなく、内容を一切変更できません。でも今回は何か大きなことをしよう. FFmpeg を再コンパイルし、JNI のインターフェイス関数を書き直しました. 今回は C から Java まで完全にオープンソースにします. 2.0 プロジェクトは 2 か月以上の空き時間を費やして、ようやく完成しました.とてもゼリーであり、このブログは、非常にゼリーであるかどうかにかかわらず、すべての著者の心からの考えを表現します. チキンゼリーに加えて, 私はまた、その軟便の効率についても文句を言いたいです. それは実際にはあまり高くありません. 3.0 でハードコーディングを試すか、2.0 のイテレーションで H265 コーディングを使用します. これは後で.しかし、WeChat が小さなビデオを大きなビデオに置き換えるリズムを見ると、それは可能であるはずです。

この記事には次のナレッジ ポイントが含まれます。

  • Andorid ビデオとオーディオのキャプチャ

  • YUV ビデオ処理 (手動カット、回転、ミラーリングなど) PCM オーディオ処理

  • FFmpeg APIを使用、YUVエンコーディングはH264、PCMエンコーディングはAAC

  • FFmpeg エンコーダーの構成

  • エンジニアリングにおける JNI の実用化

  • AndroidでのFFmpegコマンドツールの作成と適用

  • プロジェクトでの Android Studio プラグイン cMake の適用

充電:

  • 少なくとも、YUV、PCM、および MP4 が何であるかを知る必要があります (ビデオおよびオーディオ コーデック テクノロジのゼロベースの学習方法)。

  • Android で使用可能な FFmpeg (libx264 と libfdk-aac を含む) を読み込んでコンパイルし、Android で実行可能なコマンドを使用して FFmpeg をコンパイルし、Android で JNI を再生することをお勧めします。

  • C/C++ の基本的な構文の基本的な理解がある。

個人の環境とツール

  • システム: macOS-10.12.5

  • コンパイラ: Android Studio-2.3.2

  • ndk: r14

  • FFmpeg: 3.2.5

★記事末尾の名刺は、音声・動画開発学習教材(FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、srs)や音声・動画学習ロードマップなどを無料で受け取ることができます。

下記参照!

2. プロジェクトの概要:

2.1 エフェクト描画:

プロジェクトのアドレスは変更されていません: GitHub - mabeijianxi/small-video-record: FFmpeg ビデオを使用して WeChat の小さなビデオを記録し、その圧縮処理を行います.ここでは、バージョン 1.0 の gif 画像を再利用します. インターフェイスはまったく変更されていないためです.機能は今のところそれほどパッケージ化されていませんが、問題ありません。後で追加されます。

2.2 全体的なプロセス:

2.3 プロジェクト カタログの閲覧:

3. 新規プロジェクト


以前とは異なる可能性がある新しいプロジェクトを作成します. C++ サポートと C++ 標準オプションを確認する必要がある場合は、以下に示すように C++ 11 を選択します。

C++ のサポートは必須であり、C++11 を選択するのには理由があります。後でいくつかの API を使用します。次に、6 つの動的ライブラリ、ヘッダー ファイル、cmdutils.c cmdutils.h cmdutils_common_opts.h config.h ffmpeg.c ffmpeg.h ffmpeg_filter をコンパイルし、Android .c ffmpeg_opt.c で利用可能な FFmpeg (libx264 と libfdk-aac を含む) でコンパイルしました。プロジェクトの cpp ディレクトリに移動します. 完了すると、cpp ディレクトリは次のようになります.

自動生成された native-lib.cpp が私よりも 1 つ多いかもしれませんが、このファイルはそれを一時的に保持します。

4. JNI インターフェイスを記述します。

新しいインターフェイス クラス FFmpegBridge.java を作成し、必要に応じて次のメソッドを一時的に定義しました。

package com.mabeijianxi.smallvideorecord2.jniinterface;import java.util.ArrayList;/** 
 * jianxi が 2017/5/12 に作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */public class FFmpegBridge { private static ArrayList<FFmpegStateListener> listeners=new ArrayList(); static { 
        System.loadLibrary("avutil"); 
        System.loadLibrary("swresample"); 
        System.loadLibrary("avcodec"); 
        System.loadLibrary("avformat"); 
        System.loadLibrary("swscale"); 
        System.loadLibrary("avfilter"); 
        System.loadLibrary("jx_ffmpeg_jni");
    public static final int ALL_RECORD_END =1; public final static int ROTATE_0_CROP_LF=0; /** 
     * 90 度回転して左上をトリミング
     */ 
    public final static int ROTATE_90_CROP_LT =1; /** 
     * まだ処理されていません
     */ 
    public final static int ROTATE_180= 2; /** 
     * 270 (-90) 回転して、左上、左右のミラーをトリミングします
     */ 
    public final static int ROTATE_270_CROP_LT_MIRROR_LR=3; /** 
     * 
     * @return ffmpeg コンパイル情報を返す
     */ 
    public static native String getFFmpegConfig(); / ** 
     * コマンド形式で ffmpeg を実行します
     * @param cmd 
     * @return は成功を示すために 0 を返します
     */ 
    private static native int jxCMDRun(String cmd[]); /**
     * ビデオの 1 フレームをエンコードし、一時的に yv12 ビデオのみをエンコード
     * @param data 
     * @return 
     */ 
    public static native int encodeFrame2H264(byte[] data); /** 
     * オーディオの 1 フレームをエンコードし、一時的に pcm オーディオのみをエンコード
     * @ param data 
     * @return 
     */ 
    public static native int encodeFrame2AAC(byte[] data); /** 
     * 記録の終了
     * @return 
     */ 
    public static native int recordEnd(); /** 
     * 初期化
     * @param debug 
     * @ param logUrl 
     */ 
    public static native void initJXFFmpeg(boolean debug,String logUrl); public static native void nativeRelease(); /** 
     * 
     * @param mediaBasePath 動画保存ディレクトリ 
     * @param mediaName 動画名
     * @param filter 回転ミラークリッピング処理
     * @param in_width 入力ビデオ幅
     * @param in_height 入力ビデオ高さ
     * @param out_height 出力ビデオ高さ
     * @param out_width 出力ビデオ幅
     * @param frameRate ビデオ フレーム レート
     * @param bit_rate ビデオ ビット レート
     * @return 
     */ 
    public static native int prepareJXFFmpegEncoder(String mediaBasePath, String mediaName, int filter, int in_width, int in_height, int out_width, int out_height, int frameRate, long bit_rate); /** * コマンド形式の実行* @param cmd */ public static 
     int 
     jxFFmpegCMDRun 
     ( 
    String cmd){ 
     * @param what 
     */ 
        String Regulation="[ \\t]+"; final String[] split = cmd.split(regulation); return jxCMDRun(split);
    } /** 
     * 底層回调
     * @param state
        }
    public static synchronized void notifyState(int state,float what){ for(FFmpegStateListener listener: listeners){ if(listener!=null){ if(state== ALL_RECORD_END){ listener.allRecordEnd() 
                    ; 
                } 
            } } 
        / 
    ** 
     *注記: 
     @param listener 
     */ 
    public static void registFFmpegStateListener(FFmpegStateListener listener){ if(!listeners.contains(listener)){ 
            listeners.add(listener);  
            listeners.remove(listener);
    } public static void unRegistFFmpegStateListener(FFmpegStateListener listener){ if(listeners.contains(listener)){ 
        } 
    } public interface FFmpegStateListener { void allRecordEnd(); 
    } 
}复制代码

これらのメソッドを作成するときは、ネイティブが定義されていないため、この時点ですべてポピュラーになります。心配する必要はありません。絡まることはありません。対応するメソッドにカーソルを合わせて、Alt + Enter を軽く押します。図に示すような効果が表示されます。

再度確認後、ネイティブでこのインターフェースを追加します。native-lib.cpp を呼び出すのがあまり好きではないので、jx_ffmpeg_jni.cpp に変更しました

5.ネイティブコードを書く


私は c/c++ をあまり使用せず、Java にも慣れているため、ネーミングに苦労することがあります。じゃあちょっと我慢~~

5.1 ログ機能の準備:

どの言語をプレイしても、丸太がなくても羊毛をプレイできるので、これが最初のステップです。新しい jx_log.cpp と jx_log.h を作成します。jx_log.h:

/** 
 * jianxi が 2017/6/2 に作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_JX_LOG_H#define JIANXIFFMPEG_JX_LOG_H#include <android/log.h>extern int JNI_DEBUG;#define LOGE(debug, format, ...) if (デバッグ){__android_log_print(ANDROID_LOG_ERROR, "jianxi_ffmpeg", format, ##__VA_ARGS__);}#define LOGI(debug, format, ...) if(debug){__android_log_print(ANDROID_LOG_INFO, "jianxi_ffmpeg", format, ##__VA_ARGS__) );}#endif //JIANXIFFMPEG_JX_LOG_H复制代码

jx_log.cpp:

/** 
 * 2017/6/2 に jianxi が作成. 
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#include "jx_log.h"int JNI_DEBUG= 1; コードをコピー

もちろん、デバッグを有効にするかどうかのフラグ JNI_DEBUG も定義します。

5.2 実行可能コマンドの FFmpeg インターフェイスを準備します。

ここでは、Android で実行可能なコマンドをコンパイルする FFmpeg を読み終えていることを前提としています。これは、以前にコピーしたソース コードにいくつかの変更を加える必要があるためです。そうしないと、動作しません。FFmpeg に接続するための 2 つの新しいファイルを作成します.ファイル内の 1 つの関数は Java レイヤー用に呼び出され、もう 1 つはネイティブ用に呼び出され、もう 1 つはデバッグ制御ログを初期化するために使用されます。

jx_ffmpeg_cmd_run.h:

/** 
 * jianxi が 2017/6/4 に作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_FFMPEG_RUN_H#define JIANXIFFMPEG_FFMPEG_RUN_H#include <jni.h>JNIEXPORT ジント JNICALLJava_com_mabeijianxi_smallvideorecord2_jniinterface_FFmpegBridge_jxCMDRunvoid(JNIEnv *env, jclass type; jobject_Arraycallback コマンド
                                                                       ; * ptr, int レベル, const char* fmt, 
                            va_​​list vl);JNIEXPORT void JNICALLJava_com_mabeijianxi_smallvideorecord2_jniinterface_FFmpegBridge_initJXFFmpeg(JNIEnv *env, jclass タイプ, 
        jboolean デバッグ, 
jstring logUrl_);int ffmpeg_cmd_run(int argc, char **argv);#endif //JIAAMMPEGIFMPEG_HFF复制代码

jx_ffmpeg_cmd_run.c:

/** 
 * 2017/6/4. jianxi 作成.. 
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#include "jx_ffmpeg_cmd_run.h"#include "ffmpeg.h"#include " jx_log.h"/** 
 * 命令行方式で実行、戻り0表示成功
 */JNIEXPORT jint JNICALLJava_com_mabeijianxi_smallvideorecord2_jniinterface_FFmpegBridge_jxCMDRun(JNIEnv *env, jclass type, 
        jobjectArray commands){ int argc = (*env)->GetArrayLength(env,commands) ; char *argv[argc]; int i; for (i = 0; i < argc; i++) { 
        jstring js = (jstring) (*env)->GetObjectArrayElement(env,commands, i); 
        argv[i] = (char *) (*env)->GetStringUTFChars(env,js, 0); 
    戻ります ffmpeg_cmd_run(argc,argv);
}int ffmpeg_cmd_run(int argc, char **argv){ return jxRun(argc, argv) 
;
}char *logUrl;/** 
 * 初始化debugツール
 */JNIEXPORT void JNICALLJava_com_mabeijianxi_smallvideorecord2_jniinterface_FFmpegBridge_initJXFFmpeg(JNIEnv *env, jclass type, 
                                                                           jboolean debug, 
                                                                           jstring logUrl_) { 
    JNI_DEBUG = デバッグ; if (JNI_DEBUG&&logUrl_!=NULL) { 
        av_log_set_callback(log_callback); const char* log = (*env)->GetStringUTFChars(env,logUrl_, 0); 
        logUrl = (char*)malloc(strlen(log)); strcpy(logUrl,ログ);
        (*env)->ReleaseStringUTFChars(env,logUrl_, log); 
    } 
}
void log_callback(void *ptr, int level, const char *fmt, 
                  va_​​list vl) { 
    FILE *fp = NULL; if (!fp) 
        fp = fopen(logUrl, "a+"); if (fp) { vfprintf(fp, fmt, vl); 
        fflush(fp); 
        fclose(fp); 
    }
}
コードをコピー

これを一気に書いてしまえば、間違いなくどこにでも流行っている. 大変なことになる. ファイルもメソッドも見つからない. それは、あまりにも多くのファイルを追加したからである. cMakeツールは知らない. 正しい方法C/C++ ファイルを追加し、CMakeLists.txt に移動して他のユーザーに伝えます。完了したら、[同期] をクリックして同期することを忘れないでください。

5.3 安全なキューを準備します。

音声や映像のデータを集めたらFFmpegに送って一連の処理を行うソフトコーディングなのでコーディング速度はCPUに大きく依存する現在のx264アルゴリズムと現在のCPUを組み合わせると追いつかない毎秒20フレーム+の速度で、フレームを直接キャプチャしてフレームをエンコードすると、フレームが確実に失われるため、キューに入れることにしました.マルチスレッドプログラミングの存在により、私たちのキューには安全が必要です。ほんの少しです。男が女の子をひったくるようなものです。女の子は当然、私のような誰かが彼女を保護する必要があります。このキューのコードは私のウェブサイトからコピーしたものなので、言うことはありません~~

threadsafe_queue.cpp

/** 
 * jianxi が 2017/5/31 に作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_THREADSAFE_QUEUE_CPP#define JIANXIFFMPEG_THREADSAFE_QUEUE_CPP#include <queue>#include <memory>#include <mutex>#include <condition_variable>/** 
 * ひとつの安全的队列
 */template<typename T>class threadsafe_queue {private: mutable std::mutex mut; std::queue<T> data_queue; std::condition_variable data_cond;public: 
    threadsafe_queue() {} 
threadsafe_queue
    (threadsafe_queue const &other) { std::lock_guard<std::mutex> lk(other.mut); 
        data_queue = other.data_queue; 
    } void push(T new_value)//入队操作
    { std::
        data_queue.push(new_value); 
    } bool try_pop(T &value)//不管有没有队首要素直返し
    { std::lock_guard<std::mutex> lk(mut); (data_queue.empty()) が false を返す場合。
        値 = data_queue.front();
        data_cond.notify_one(); 
    } void wait_and_pop(T &
    { std::unique_lock<std::mutex> lk(mut); 
        data_cond.wait(lk, [this] { return !data_queue.empty(); }); 
        値 = data_queue.front(); 
        data_queue.pop(); 
    } std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); 
        data_cond.wait(lk, [this] { return !data_queue.empty(); }); std::shared_ptr<T> res(std::make_shared<T>(data_queue.front())); 
        data_queue.pop(); res を返します。
        data_queue.pop(); true を返します。
    } std::shared_ptr<T> try_pop() { std::lock_guard<std::mutex> lk(mut); if (data_queue.empty()) return std::shared_ptr<T>(); std::shared_ptr<T> res(std::make_shared<
        data_queue.pop(); res を返します。
    } bool empty() const { return data_queue.empty();
    } 
};#endif //JIANXIFFMPEG_THREADSAFE_QUEUE_CPP复制代码

ここで使用されているいくつかのライブラリは C++11 標準にあります~

5.4 構成情報を格納するための構造を準備します。

実際、これは JavaBean に似ており、コードに直接関与し、コード内の JXJNIHandler フィールドは当分見られません。

jx_user_arguments.h:

/** 
 * 2017/5/26 に jianxi によって作成されました. 
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_JX_USER_ARGUMENTS_H#define JIANXIFFMPEG_JX_USER_ARGUMENTS_H#include "jni.h"class JXJNIHandstr UserArguments { const char *media_base_path; //ファイル格納アドレス
    const char *media_name; //ファイル コマンド プレフィックス
    char *video_path; //ビデオ格納アドレス
    char *audio_path; //オーディオ格納アドレス
    char *media_path; //合成された MP4 格納アドレス
    int in_width; //出力幅
    int in_height; //入力高さ
    int out_height; //出力高さ
    int out_width; //出力幅
    int frame_rate; //ビデオ フレーム レート制御
    long long video_bit_rate; //ビデオ ビット レート制御
    int audio_bit_rate; //オーディオビットレート制御
    int audio_sample_rate; //オーディオ サンプル レート コントロール (44100) 
    int v_custom_format; //いくつかのフィルター 操作コントロール
    JNIEnv *env; //env グローバル ポインタ
    JavaVM *javaVM; //jvm ポインタ
    jclass java_class; //Java インターフェイス クラス
    JXJNIHandler の calss オブジェクト *handler; // グローバル処理オブジェクトへのポインタ} ;#endif //JIANXIFFMPEG_JX_USER_ARGUMENTS_H

この構造は、プロセス全体で使用されます。

5.5 ビデオ (YUV) エンコーディング コードの記述

このセクションは、この記事の核となる部分の 1 つです。

一部の兄弟は、フレームを合成するためにフレームをエンコードしないのはなぜかと尋ねるかもしれません.なぜなら、私は合成時間をテストしたからです.それは基本的にミリ秒レベルであり、あまりにも面倒です.私がこれを行う場合は、FFmpegコマンドツールを直接使用します.わずか数行のコード

コードが貼り付けられたので、その過去と現在の生活について話させてください、それは非常に重要です〜。

1) ビデオエンコーダのパラメータ設定

パラメータについてしばらく文句を言わなかった場合は、ここでそれを開いて、ffmpeg エンコーダー AVCodecContext の構成パラメーターを詳しく調べることができます。

    size_t path_length = strlen(引数->video_path); char *out_file = (char *) malloc(path_length + 1); strcpy(out_file, arguments->video_path);复制代码

上記のコードを使用して、ビデオ出力アドレスをコピーしました。次の avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file) 関数がその正当性をチェックし、サフィックスに従って、ビデオ出力アドレスが .h264 で終わることが非常に重要です。 format pFormatCtx の割り当てに対応します。

  • pCodecCtx->codec_id = AV_CODEC_ID_H264 ここでエンコーダ ID を指定しますが、これは間違いなく H264 です。

  • pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; エンコードされたデータ形式を指定します。

  • pCodecCtx->bit_rate = 引数->video_bit_rate、ビデオ ビット レートを指定します。このパラメータは非常に重要で、ビデオの品質とサイズを大きく決定しますが、これによると、VBR モードのビット レート モードにも関連しています。一定の変動があります。

  • pCodecCtx->thread_count = 16 スレッドの数、私はここでそれを死ぬまで書きました。あまり良くありません。道路上の友人は、コアの数を伴うには 1.5 で十分だと言いました。

  • pCodecCtx->time_base.num = 1; pCodecCtx->time_base.den = 引数->frame_rate この 2 つはフレーム レートを制御し、num は分母、den は分子で、フレーム レートは除算されます。収集したフレーム レートと同じである必要があります。ここで非常に重要です。そうしないと、ビデオとオーディオが同期しなくなり、通り過ぎてしまう可能性があります。カメラに設定したフレーム数は必ずしも正しいとは限りません。実際のフレーム番号が保存されます. このとき、Java レイヤーに接続するときに後で説明する、ビデオとオーディオの同期がずれる原因にもなります.

  • av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0) これは符号化速度を指定するためのプリセット値で、とりあえず最速としてハードコーディングしました。

  • pCodecCtx->qmin pCodecCtx->qmax これは量子化範囲の設定です。値の範囲は 0 から 51 で、値が小さいほど品質が高くなり、必要なビット レートが高くなります。0 はロスレス エンコーディングを意味します。符号化のプロセスと原理については、ビデオ圧縮符号化と音声圧縮符号化の基本原理を読むことができます。

  • pCodecCtx->max_b_frames = 3 最大 b フレームは 3 です。これを 0 に設定すると、エンコードが高速になります。これは、動き予測と動き補償のエンコード時間が i、b、p フレームに分割されるためです。 Raytheon: I フレームはこのフレームのみを使用します。エンコード処理中にモーション推定とモーション補償を必要としません。明らかに、Iフレームは時間方向の相関がなくなるわけではないので、圧縮率は相対的に低くなります。P フレームの符号化プロセスでは、前の I フレームまたは P フレームが動き補償の参照画像として使用され、現在の画像と参照画像の差分が実際に符号化されます。B フレームの符号化方法は P フレームの符号化方法と似ていますが、唯一の違いは、符号化プロセス中の予測に前の I フレームまたは P フレームと後続の I フレームまたは P フレームを使用することです。各 P フレームの符号化では、参照画像として 1 フレームの画像を使用する必要があるのに対し、B フレームでは 2 フレームの画像を参照として使用する必要があることがわかります。対照的に、B フレームは P フレームよりも圧縮率が高いため、B フレームが多いと一定の遅延が発生します。

  • av_dict_set(¶m, "profile", "baseline", 0) 出力を特定の H.264 プロファイルに制限できます。すべてのプロファイルには、ベースライン、main.high、high10、high422、high444 が含まれます。プロファイル オプションはロスレス エンコーディングと互換性がありません。

2) Android カメラによって収集された YUV データ構造

最初に YUV 形式について簡単に説明します. RGB と同様に, YUV も色分け方法です. Y: 明るさ (ルミナンスまたはルマ), つまりグレースケール値を表します. U と V: クロマ (クロミナンスまたはクロマ) を表します.この関数は、画像の色と彩度を記述することであり、ピクセルの色を指定するために使用されます。Y しかない場合は、白黒のオーディオとビデオです。さまざまなサンプリング方法によると、主に YUV4:4:4、YUV4:2:2、および YUV4:2:0 があります。YUV 4:4:4 サンプリングで、各 Y は一連の UV コンポーネントに対応します。YUV 4:2:2 サンプリング、2 つの Y ごとに一連の UV コンポーネントを共有します。YUV 4:2:0 サンプリング、4 つの Y ごとに一連の UV コンポーネントを共有します。たとえば、画面に 8 つのピクセルがある場合、YUV4:4:4 には 8 つの Y、8 つの U、および 8 つの V があります。YUV4:2:2 には、8 つの Y、4 つの U、および 4 つの V があります。YUV4:2:0 には 8 つの Y、2 つの U、および 2 つの V があります。収集したデータを処理するには、そのデータ型とデータ構造を知る必要があります. 古いバージョンの android SDK では、YV12 と NV12 の 2 つのモードでしかデータを収集できません. どちらも YUV420 に属しますが、もう一方は配置構造が異なります。下の写真を見てみましょう. もちろん、元の写真が間違っているので、下の最初の写真をフォトショップで加工しましたが、私は年をとっており、私の手は完璧ではないので、それを読んだだけです.

物理的に類似した 4 つのピクセル Y1、Y2、Y7、および Y8 が同じ U1 および V1 を共有し、同様の Y3、Y4、Y9、および Y10 が U2 および V2 を使用することがわかります。ここでのさまざまな色は、この機能を非常に鮮やかに表現しており、一目でわかります. グリッドの数は、この画像のフレームのバイト配列のサイズであり、配列要素の配置順序は、後ろの長いバーと同じです。NV12 は次のとおりです。

UVの発光位置が違うだけであることがわかります。

3) YV12データ処理

YV12 と NV12 の両方を使用することができます. カメラのパラメーターを設定するときに YV12 を選択しました. 次に、ビデオの切り取りと回転を実現するためのいくつかの簡単なアルゴリズムを記述します. これは非常に簡単です.時に。

ここでは、収集したビデオが幅 640、高さ 480 であると仮定し、幅 400、高さ 300 のビデオにカットしたいと考えています。上記の知識によると、640 480 のバイト配列のフレームに 640 480 Y があり、それが一番上にあり、次に (1/4) 640 480 Vがあり、次にそこにあると指定できます。 (1/4) 640 480 A Uになります。これを 400 300 に分割します。当然、データの一部を保持する必要があります。最初に Y のモデルを作成します。これは 640 480 であるため、下の図に示すように、640 Y の行、合計 480 行と見なすことができます。赤いマークは 640*480 Y を示し、黄色のマークは Y を示します。領域は、Y のすべての値をクリップします。

画像の向きに注意してください。このモデルを使用すると、配列を操作するコードを記述できます。コードの一部を次に示します。

カット Y:

        unsigned char *in_buf; unsigned char *out_buf_y; for(int i=480-300;i<480;i++){//Traverse high 
            for(int j=0;j<400;j++){//Traverse wide 
                int index =640*i+j;//現在トラバースされている添え字の符号なし char 値
                =*(in_buf+index);//現在の添え字の下の Y 値//ターゲット配列への割り当てを開始
                *(out_buf_y+(i- (480- 300))*400+j)=value;//対象配列は400*300、ここでは0コーナーマークから順にトラバースして代入します} } 
            copy 
        code

in_buf を YV12 ビデオ データのフレームとすると、このサイクルを実行すると、カットされた Y 値が取得されます.次に、カットされた UV データを分析します.UV モデルは Y とは少し異なります. YUV4:2:0と呼ばれる理由は、Vがないからではなく、実際には1行目にUをスキャンし、2行目にVをスキャンし、2行目にUをスキャンするなど、垂直方向にUV交換スキャンを行っています。三行目。水平方向は、1列おきにスキャンされ、例えば1列目がスキャンされた場合、2列目はスキャンされず、3列目がスキャンされます。したがって、水平および垂直方向の U のデータはその Y の 1/2 であり、合計数はその値の 1/4 であり、V も同じです。これらを知っていれば、モデルを簡単に構築できます。

320~240の領域が当社のU値またはV値である領域であり、 200~150の領域が当社のカットU値またはV値の対象領域です。コードは以下のように表示されます:

UV をカットする:

unsigned char *in_buf; unsigned char *out_buf_u; unsigned char *out_buf_v; for(int i=(480-300)/2;i<480/2;i++){//Traverse high for(int j=0;j 
            < 400/2;j++){//traversal width 
int
                index=(640/2)*i+j;//unsigned char 
                v=*(in_buf+(640*480)+index);/ /現在のコーナー マーク (Y が前に配置されているため、ポインターの位置を最初に 640*480 単位戻す必要があります) 
unsigned
                char u=*(in_buf+(640*480*5/4)+index); //U現在の添字の下の値 (Y と V が前に配置されるため、ポインターの位置は 640*480*5/4 単位だけ後方に移動する必要があります)//添字 0 からターゲット配列 out_buf_u に割り当てられます *(out_buf_u+( 
                i- (480-300)/2)*400/2+j)=u; 
                *(out_buf_v+(i-(480-300)/2)*400/2+j)=v; 
            } 
        } コードをコピー

上記の操作の後、最も基本的なカットが完了しました. カメラによって収集されたデータは水平です. 垂直に記録し、何も操作を行わない場合、記録されたビデオは反時計回りに90°回転します. Tnd, 反時計回りに行く場合、兄弟はあなたを時計回りに90°回転させるので、まっすぐにする必要があります。

上記の図に示すように、カットする必要がある位置は変更されないため、for ループは変更されず、出力配列の排出位置のみが変更され、元の最初の行が最後の列に配置されます。 2 番目の行は、これから推測される最後から 2 番目の列に配置されます。以下もコードで示しています。

Y シヤーと時計回りに 90° 回転します。

unsigned char *in_buf; unsigned char *out_buf_y; for(int i=(480-300);i<480;i++){//トラバース高さ
                for(int j=0;j<400;j++){//トラバース幅
int
                    index=(640)*i+j;//Unsigned char 
value
                    =*(in_buf+index);//現在のコーナー マークの下の Y 値
*
                    (out_buf_y+j*300+ (300-(i- (480-300)-1)))=value;// 出力配列の画像を組み合わせて理解する
                } 
            }コードをコピー

YがUVで終わったら非常に簡単です、私たちはすでに法則を習得しているので、水平方向と垂直方向の両方のUVの値はYの半分です.

UV をカットする:

            unsigned char *in_buf; unsigned char *out_buf_u; unsigned char *out_buf_v; for(int i=(480-300)/2;i<480/2;i++){//Traverse high for(int j=0;j 
                < 400/2;j++){//traversal width 
int
                    index=(640/2)*i+j;//unsigned char 
value_v
                    =*(in_buf+(640*480)+index); //現在の V 値コーナー マーク
                    unsigned char value_u=*(in_buf+(640*480*5/4)+index);//現在のコーナー マーク下の U 値 * ( 
out_buf_u
                    +j*300/2+( 300/2-(i- (480-300)/2-1)))=value_u;//出力配列の画像を組み合わせて理解
                    *(out_buf_v+j*300/2+(300/2-(i- (480-300))/ 2-1)))=value_v;// 出力配列のイメージを組み合わせて理解する
                } 
            }コードをコピー

フロントカメラなので鏡像になりますので、フロントカメラで録画する場合は鏡像処理をする必要があります. 詳しくはソースコードを参照してください. これら以外にも面白い操作がたくさんできます. UV値を128に割り当てた場合など、黒いバー画像になった場合の明るさや色合いなども調整できます。

データを加工したらFFmpegエンコーディングのAPIを呼び出します。

5.6 オーディオエンコーディング

上記のフローチャートを見ると、動画と同様の手順で、データ量も比較的少ないことが分かりますが、libfdk-aacを使ってコンパイルすると、基本的には収集速度に追いつくことができます。 、次にチャット:

jx_pcm_encode_aac.h:

/** 
 * jianxi が 2017/5/18 に作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_JX_PCM_ENCODE_AAC_H#define JIANXIFFMPEG_JX_PCM_ENCODE_AAC_H#include "base_include.h"#include "jx_user_arguments.h"using namespace std;/** 
 * pcm编码がaac 
 */class JXPCMEncodeAAC {public: 
    JXPCMEncodeAAC(UserArguments* arg);public: int initAudioEncoder(); static void* startEncode(void* obj); void user_end(); int sendOneFrame(uint8_t* buf); int encodeEnd();private: int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index);private: 
    threadsafe_queue<uint8_t *> frame_queue; 
    AVFormatContext *pFormatCtx; 
    AVOutputFormat *fmt; 
    AVStream *audio_st; 
    AVCodecContext *pCodecCtx; 
    AVCodec *pCodec; 
AVFrame
    *pFrame;
    AVPacket パケット; int got_frame = 0; int ret = 0; int サイズ = 0; int i; int is_end = 0; 
    UserArguments *引数; 
    ~JXPCMEncodeAAC() { 
    } 
};#endif //JIANXIFFMPEG_JX_PCM_ENCODE_AAC_H コピーコード

オーディオについてはあまり研究していません. 以下はパラメーターの簡単な紹介です. よりアクセスしやすいビデオおよびオーディオ データ処理エントリ: PCM オーディオ サンプリング データ処理

エンコード パラメータ:

  • pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16 サンプリング形式を設定します。ours は 16 ビットの符号なし整数で、Java オーディオが収集されるときに設定されるパラメータに対応する必要があります。

  • pCodecCtx->sample_rate = arguments->audio_sample_rate サンプリング レート、オーディオは私たちにとって最も重要なことではありません。ここでは主流の 44100 を書きました。ここでも、Java オーディオ収集時に設定されるパラメータに対応する必要があります。

  • pCodecCtx->channel_layout = AV_CH_LAYOUT_MONO; pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout) これはチャンネル数を設定するためのもの. オーディオ要件は高くないので、パラメータに対応する必要がある単一のチャンネルを使用します. Javaオーディオ取得時に設定します。AV_CH_LAYOUT_STEREO はステレオ 2 チャンネル、AV_CH_LAYOUT_4POINT0 は 4 チャンネルなど、多くのオプションがあります。

  • pCodecCtx->bit_rate = 引数->audio_bit_rate オーディオ ビット レート。

パラメータを設定したら、残りは FFmpeg に引き渡されます。

5.7 動画合成クラスを書く

オーディオとビデオがエンコードされたら、それらを mp4 に合成する必要があります.これで、用意した FFmpeg コマンド ツールを使用できます.アドレスを投げるだけでよい.この合成プロセスは非常に短時間で済みます.

jx_media_muxer.h:

/** 
 * 2017/5/24 jianxi 作成。
 * https://github.com/mabeijianxi 
 * [email protected] 
 */#ifndef JIANXIFFMPEG_JX_MEDIA_MUXER_H#define JIANXIFFMPEG_JX_MEDIA_MUXER_H#include "base_include.h"class JXMediaMuxer{public: int startMuxer(const char * video, const char *audio , const char *out_file);private: 
}
;#endif //JIANXIFFMPEG_JX_MEDIA_MUXER_H

元のリンク: FFmpeg を使用して Android のビデオ録画と圧縮を再生する - Nuggets

★記事末尾の名刺は、音声・動画開発学習教材(FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、srs)や音声・動画学習ロードマップなどを無料で受け取ることができます。

下記参照!

 

おすすめ

転載: blog.csdn.net/yinshipin007/article/details/130116176