この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、記事の最後で紹介します)
Androidのスマホは、MP4形式しか動画ファイルは再生できません。
ビデオカメラで撮影した動画ファイルは、MTS形式が多い。
MTS形式の動画ファイルをAndroidで再生するには、MP4形式に変換する必要があります。
Android13から、AIFF形式(Apple標準のオーディオ形式)の音楽ファイルが再生できなくなっています。
CDの曲をiTunesに取り込んだ音楽ファイルをAndroid13以降で再生するには、MP3形式に変換する必要があります。
FFmpegは、動画ファイルや音声ファイルを記録・変換・再生するためのフリーソフトウェアです。
Androidで対応していない形式のファイルはFFmpegを使用して、MP4形式やMP3形式に変換すれば、Androidでも再生できるようになります。
FFmpegKitは、Androidで使用できるオープンソフトウェアライブラリです。
ffmpegkitを使用するための準備
ffmpegkitを使用するには、
プロジェクトおよび、モジュールのbuild.gradleファイルに定義の追加が必要です。
◎build.gradle(プロジェクト)
:
allprojects {
repositories {
mavenCentral()
:
}
}
:
◎build.gradle(モジュール)
2024年2月現在の最新バージョンは 6.0.2 です。
dependencies {
:
implementation 'com.arthenica:ffmpeg-kit-full:6.0-2'
}
◎ライセンス表記
ffmpegkitは、LGPL v3.0 Licenseです。
アプリで使用する場合、ライセンス表記が必要です。
動画ファイルの形式を変換する
ffmpegkitは、コマンドライン引数で動画ファイルの変換動作をパラメータで指定します。
フォーマット、コーデック、開始位置や長さ、品質指定などが指定できます。
これらパラメータ指定の組み合わせで、MP4形式変換、サムネイルの作成、動画のトリミングが可能です。
メディアストア(動画フォルダ)の動画ファイルにアクセスするには、きめ細かいメディア権限を設定する必要があります。
詳細は、Android13対応(ファイルのメディア権限)で紹介しています。
MP4形式変換
FFmpegKitは、動画ファイルの形式は拡張子で判断しています。
入力ファイルがMP4形式以外の拡張子で、出力ファイルの拡張子がMP4形式であれば、MP4形式に変換できます。
import com.arthenica.ffmpegkit.FFmpegKit;
import com.arthenica.ffmpegkit.FFmpegKitConfig;
import com.arthenica.ffmpegkit.FFmpegSession;
import com.arthenica.ffmpegkit.ReturnCode;
:
String[] mimeList = new String[]{"video/mp4", "video/webm", "video/quicktime", "video/mp2ts"};
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, "_ID DESC");
while (cursor.moveToNext()) {
if (Arrays.asList(mimeList).contains(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE)))) {
Uri contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, Long.parseLong(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))));
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DATA));
String source = cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DISPLAY_NAME));
String ext = source.substring(source.lastIndexOf(".")).toLowerCase();
long width = Long.parseLong(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.WIDTH)));
long height = Long.parseLong(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.HEIGHT)));
// 拡張子がMP4以外はコーディック
if (!ext.equals(".mp4") && ext.length() > 0) {
// ファイル名からスペース削除と拡張子変更
String file = source.toLowerCase().replace(" ", "").replace(" ", "").replace(ext, ".mp4");
path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + file;
// mp4ファイル変換
String mts = FFmpegKitConfig.getSafParameterForRead(context, contentUri);
String mp4 = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + file;
FFmpegSession session = FFmpegKit.execute(String.format("-i '%s' -qmin %d -qmax %d -acodec libmp3lame -ab 192000 -ar 48000 -s %dx%d '%s'", mts, 16, 20, width ,height , mp4));
if (ReturnCode.isSuccess(session.getReturnCode())) {
// 変換成功
:
} else {
// 変換失敗
:
}
}
}
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
});
コンテンツリゾルバを使用して、メディアストア(動画フォルダ)の動画ファイルのリストを取得しています。
また、MIMEタイプがmp4、webm、quicktime(MOV)、mp2ts(MTS)の動画ファイルか判定しています。
コンテンツリゾルバのクエリ結果より、Uri、パス、ファイル名、拡張子、サイズを取得します。
MP4形式以外の場合、MP4形式に変換します。
FFmpegKitの引数として、入力ファイルパス(-i)、品質指定(-qmin, -qmax)、音声コーディック(-acodec)、音声サンプリングレートレート(-ar)、音声ビットレート(-ab)、サイズ(-s 横*縦)、出力ファイルパスを指定しています。
動画コーディックは、デフォルト(指定なし)を使用します。
変換結果は、FFmpegSessionで判定します。
サムネイル画像の作成
動画ファイルの開始位置とフレーム数に1を指定することで、サムネイル画像ファイルを出力できます。
import com.arthenica.ffmpegkit.FFmpegKit;
:
String[] mimeList = new String[]{"video/mp4", "video/webm", "video/quicktime", "video/mp2ts"};
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, "_ID DESC");
while (cursor.moveToNext()) {
if (Arrays.asList(mimeList).contains(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE)))) {
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DATA));
long width = Long.parseLong(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.WIDTH)));
long height = Long.parseLong(cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.HEIGHT)));
String jpg = String.format("%s.jpg", cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.TITLE)));
// サムネイル作成
FFmpegKit.execute(String.format("-i '%s' -ss 0 -vframes 1 -s %dx%d '%s'", path, width ,height , context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + jpg));
}
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
});
コンテンツリゾルバを使用して、動画フォルダにある動画ファイルのリストを取得しています。
また、MIMEタイプが。mp4、webm、quicktime(MOV)、mp2ts(MTS)を動画ファイルと判定しています。
コンテンツリゾルバのクエリ結果より、Uri、パス、ファイル名、拡張子、サイズを取得します。
FFmpegKitの引数として、入力ファイルパス(-i)、開始位置(-ss)、フレーム数(-vframes)、サイズ(-s 横*縦)、出力ファイルパスを指定しています。
動画からサムネイルを作成するで、別の方法を紹介しています。
動画上でサムネイル画像の位置が指定できるか、できないかの違いがあります。
動画のトリミング
動画ファイルの開始位置(秒)と長さ(秒)を指定することで、トリミングした動画ファイルを出力できます。
import com.arthenica.ffmpegkit.FFmpegKit;
import com.arthenica.ffmpegkit.FFmpegKitConfig;
import com.arthenica.ffmpegkit.FFmpegSession;
import com.arthenica.ffmpegkit.ReturnCode;
:
// 動画トリミング実行
trimMp4(context, uri, String.format("%s.mp4", title), new long[]{5, 30});
:
// 動画トリミング //
@SuppressLint({"Range", "DefaultLocale"})
public void trimMp4(Context context, Uri uri, String mp4, long[] values) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
// 内部ストレージにワークファイルを作成する
File work = new File(context.getFilesDir(), mp4);
work.delete();
String in = FFmpegKitConfig.getSafParameterForRead(context, uri);
String out = work.getAbsolutePath();
FFmpegSession session = FFmpegKit.execute(String.format("-ss %d -i '%s' -t %d %s'", values[0], in, values[1] - values[0], out));
if (ReturnCode.isSuccess(session.getReturnCode())) {
OutputStream outputStream;
try (FileInputStream fileinputStream = new FileInputStream(work)){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues content = new ContentValues();
content.put(MediaStore.MediaColumns.DISPLAY_NAME, mp4);
content.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
content.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
outputStream = context.getContentResolver().openOutputStream(context.getContentResolver().insert(MediaStore.Video.Media.getContentUri("external"), content));
} else {
outputStream = Files.newOutputStream(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).toString(), mp4).toPath());
}
if (outputStream != null) {
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
byte[] buffer = new byte[1024];
int len;
while ((len = fileinputStream.read(buffer)) != -1) {
bufferedOutputStream.write(buffer, 0, len);
}
bufferedOutputStream.close();
}
work.delete();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// トリミング失敗
:
}
});
}
入力ファイルをトリミングした動画ファイル(ワークファイル)を内部ストレージに出力しています。
ワークファイルをコンテンツリゾルバに登録して、動画フォルダにコピーしています。
FFmpegKitの引数として、開始位置(-ss)、入力ファイルパス(-i)、長さ(-t)、出力ファイルパスを指定しています。
変換結果は、FFmpegSessionで判定します。
音声ファイルの形式を変換する
ffmpegkitは、コマンドライン引数で音声ファイルの変換動作をパラメータで指定します。
フォーマット、コーデックなどが指定できます。
これらパラメータ指定の組み合わせで、MP3形式の音声ファイルに変換が可能です。
メディアストア(音声フォルダ)の音声ファイルにアクセスするには、きめ細かいメディア権限を設定する必要があります。
詳細は、Android13対応(ファイルのメディア権限)で紹介しています。
AIFF形式からMP3形式に変換
フォーマットにmp3(MP3)、音声コーデックにMP3エンコーダー(libmp3lame)を指定すれば、MP3形式に変換できます。
import com.arthenica.ffmpegkit.FFmpegKit;
import com.arthenica.ffmpegkit.FFmpegKitConfig;
import com.arthenica.ffmpegkit.FFmpegSession;
import com.arthenica.ffmpegkit.ReturnCode;
:
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, "IS_MUSIC != 0", null, sortOrder);
while (cursor.moveToNext()) {
// ファイル名 : MIMEタイプ
String source = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DISPLAY_NAME));
Uri contentUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, Long.parseLong(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))));
String[] aif1 = new String[]{"audio/x-aiff", "audio/aiff"};
musicItem = null;
// AIFF形式の判定
if (Arrays.asList(aif1).contains(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE)))) {
// ファイル名からスペース削除と拡張子変更
String file = source.toLowerCase().replace(" ", "").replace(" ", "").replace(".aif", ".mp3");
// mp3ファイル変換
String aif = FFmpegKitConfig.getSafParameterForRead(context, contentUri);
String mp3 = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + File.separator + file;
FFmpegSession session = FFmpegKit.execute(String.format("-i '%s' -f mp3 -acodec libmp3lame -ab %d -ar %d '%s'", aif, 192000, 44100, mp3));
if (ReturnCode.isSuccess(session.getReturnCode())) {
// 変換成功
:
} else {
// 変換失敗
:
}
}
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
});
コンテンツリゾルバを使用して、メディアストア(音声フォルダ)の音声ファイルのリストを取得しています。
また、MIMEタイプがx-aiff(AIFF)、aiffの音声ファイルをMP3変換の対象と判定しています。
コンテンツリゾルバのクエリ結果より、Uri、ファイル名を取得します。
FFmpegKitの引数として、入力ファイルパス(-i)、フォーマット(-f)、音声コーディック(-acodec)、音声サンプリングレートレート(-ar)、音声ビットレート(-ab)、出力ファイルパスを指定しています。
変換結果は、FFmpegSessionで判定します。
動画や音声のファイルを削除する
動画や音声のファイルを削除する場合、コンテンツリゾルバを経由して削除する必要があります。
private final ActivityResultLauncher<IntentSenderRequest> resultLauncher = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
context.getContentResolver().delete(deleteUri, null, null);
mainActivity.duelDatabaseHelper.deleteMovie(deleteMovie, deleteChapter);
} catch (SecurityException e) {
mainActivity.toastMessage(R.string.delete_error, mainActivity.getAlarm());
Log.d(TAG, String.format("resultLauncher:%s", e.getMessage()));
e.printStackTrace();
}
}
}
}
);
:
private void deleteVideoFile(Context context, Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
context.getContentResolver().delete(uri, null, null);
} catch (SecurityException e) {
if (e instanceof RecoverableSecurityException) {
RecoverableSecurityException recoverableSecurityException = (RecoverableSecurityException) e;
IntentSender intentSender = recoverableSecurityException.getUserAction().getActionIntent().getIntentSender();
if (intentSender != null) {
IntentSenderRequest intentSenderRequest = new IntentSenderRequest.Builder(intentSender).build();
resultLauncher.launch(intentSenderRequest);
}
}
}
} else {
new File(uri.getPath()).delete());
}
}
コンテンツリゾルバを経由して削除する場合、対象のファイルの所有者(作成したアプリ)のチェックを行っています。
別のアプリで作成したファイルを削除しようとした場合、SecurityExceptionが発生します。
削除するには、IntentSenderRequestでユーザの同意を求めます。
ActivityResultLauncherで結果を受け取り、同意を得られた場合、再度削除を実行します。
FFmpegを使用して、動画ファイルの形式を変換しているAndroidアプリです。
FFmpegを使用して、音声ファイルの形式を変換しているAndroidアプリです。
Bluetooth接続なので、Androidデバイスにも使えますね♪
今回は、ここまでです。
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪
これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪
コメント欄