teratail header banner
teratail header banner
質問するログイン新規登録
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android開発

Android開発は、Android Studioなどを使ったスマートフォンアプリの開発に関する投稿に使われます。KotlinやJavaでの実装、UI設計、パーミッション設定などが主なトピックです。

Q&A

解決済

1回答

136閲覧

androidx.media3.transformerでの動画変換で、独自のEncoderFactoryを実装した時に、MediaCodecを非同期モードで動かしたい

drednote

総合スコア345

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android開発

Android開発は、Android Studioなどを使ったスマートフォンアプリの開発に関する投稿に使われます。KotlinやJavaでの実装、UI設計、パーミッション設定などが主なトピックです。

0グッド

0クリップ

投稿2025/06/30 07:19

編集2025/06/30 07:44

0

0

実現したいこと

androidのJetpackのライブラリ media3内のtransformerクラスを使い、media3が対応している動画を独自形式の動画に変換しようとしています。
独自形式ですのでエンコーダー用Codecと、そのCodecのインスタンスを生成するEncoderFactory自作の物です。
とりあえず実験として、エンコーダーに来た映像データをJPEG化しています。

発生している問題・分からないこと

どうやらTransformerのデコーダ内部で動いているMediaCodecが同期モードになっているようで、変換処理が重いとフレームドロップしますし、スレッドにわけまくると普通に胴切れを起こします。
Transformer内部で動いているデコーダーのMediaCodecを非同期モードで動かせれば良いはずなのですが、なにか手は無いでしょうか?

該当のソースコード

Java

1//Transformer起動部分 2//変換先Codecがmp4になっていますが、ここは無視してください。関係無くなります。 3 EditedMediaItem srcItem ; 4 MediaItem srcMediaItem = new MediaItem.Builder() 5 .setUri(uri) 6 .build() ; 7 8 srcItem = new EditedMediaItem.Builder(srcMediaItem) 9 .build() ; 10 11 Codec.EncoderFactory encoderFactory = new mjpegEncoderFactory(this) ; 12 13 Transformer transform = new Transformer.Builder(this) 14 .setVideoMimeType(MimeTypes.VIDEO_H264) 15 .setAudioMimeType(MimeTypes.AUDIO_AAC) 16 .setEncoderFactory(encoderFactory) 17 .addListener(new Transformer.Listener() { 18 @Override 19 public void onCompleted(Composition composition, ExportResult exportResult) { 20 Log.i("mjpegMakeTransMainActivity:Transformer.Builder", "onCompleted") ; 21 Transformer.Listener.super.onCompleted(composition, exportResult); 22 } 23 24 @Override 25 public void onError(Composition composition, ExportResult exportResult, ExportException exportException) { 26 Log.i("mjpegMakeTransMainActivity:Transformer.Builder", "onError") ; 27 Transformer.Listener.super.onError(composition, exportResult, exportException); 28 } 29 30 @Override 31 public void onFallbackApplied(Composition composition, TransformationRequest originalTransformationRequest, TransformationRequest fallbackTransformationRequest) { 32 Log.i("mjpegMakeTransMainActivity:Transformer.Builder", "onFallbackApplied") ; 33 Transformer.Listener.super.onFallbackApplied(composition, originalTransformationRequest, fallbackTransformationRequest); 34 } 35 }) 36 .build() ; 37 38 File file = getExternalFilesDir(null) ; 39 40 transform.start(srcItem, file.getPath() + "/tmpout.mp4"); 41

Java

1// getOutputBufferで返すoutputBuffer は未設定です。ここは無視してください 2 3package com.tmptest.jpegmaketest; 4 5import static androidx.media3.common.Format.NO_VALUE; 6 7import android.graphics.Bitmap; 8import android.graphics.Canvas; 9import android.graphics.ImageFormat; 10import android.graphics.Rect; 11import android.graphics.YuvImage; 12import android.media.Image; 13import android.media.ImageReader; 14import android.media.MediaCodec; 15import android.os.Environment; 16import android.os.Handler; 17import android.os.HandlerThread; 18import android.os.Looper; 19import android.util.Log; 20import android.view.PixelCopy; 21import android.view.Surface; 22import android.widget.FrameLayout; 23 24import androidx.annotation.Nullable; 25import androidx.media3.common.Format; 26import androidx.media3.common.MimeTypes; 27import androidx.media3.common.util.UnstableApi; 28import androidx.media3.decoder.DecoderInputBuffer; 29import androidx.media3.transformer.Codec; 30import androidx.media3.transformer.ExportException; 31 32import java.io.File; 33import java.io.FileNotFoundException; 34import java.io.FileOutputStream; 35import java.io.IOException; 36import java.nio.ByteBuffer; 37 38@UnstableApi 39public class jpegMakeCodec implements Codec { 40 MainActivity activity ; 41 Format configurationFormat ; 42 Format outputFormat ; 43 Surface inputSurface ; 44 ByteBuffer outputBuffer ; 45 MediaCodec.BufferInfo outputBufferInfo ; 46 ImageReader iReader ; 47 HandlerThread outputThread ; 48 HandlerThread fileSaveThread ; 49 Rect copyRect ; 50 Bitmap drawBase ; 51 int frameNo ; 52 int no ; 53 54 public jpegMakeCodec(MainActivity useAct, Format configurationBaseFormat) { 55 activity = useAct ; 56 57 configurationFormat = configurationBaseFormat ; 58 59 outputFormat = configurationFormat.buildUpon() 60 .build() ; 61 62 iReader = ImageReader.newInstance(configurationBaseFormat.width, configurationFormat.height, ImageFormat.YUY2, 1) ; 63 inputSurface = iReader.getSurface() ; 64 65 outputBuffer = ByteBuffer.allocate(outputFormat.width*outputFormat.height*4*2) ; 66 outputBufferInfo = new MediaCodec.BufferInfo() ; 67 outputBufferInfo.set(0, outputBuffer.remaining(), 0, 0); 68 69 outputThread = new HandlerThread("OutputThread") ; 70 outputThread.start() ; 71 72 fileSaveThread = new HandlerThread("FileSave") ; 73 fileSaveThread.start() ; 74 75 copyRect = new Rect(0, 0, configurationFormat.width, configurationFormat.height) ; 76 drawBase = Bitmap.createBitmap(configurationFormat.width, configurationFormat.height, Bitmap.Config.ARGB_8888) ; 77 78 no = 0 ; 79 frameNo = 0 ; 80 } 81 82 @Override 83 public Format getConfigurationFormat() { 84 return configurationFormat; 85 } 86 87 @Override 88 public String getName() { 89 return "testEncoder"; 90 } 91 92 @Override 93 public Surface getInputSurface() { 94 return inputSurface; 95 } 96 97 @Override 98 public boolean maybeDequeueInputBuffer(DecoderInputBuffer inputBuffer) throws ExportException { 99 return false; 100 } 101 102 @Override 103 public void queueInputBuffer(DecoderInputBuffer inputBuffer) throws ExportException { 104 105 } 106 107 @Override 108 public void signalEndOfInputStream() throws ExportException { 109 110 } 111 112 @Override 113 public Format getInputFormat() throws ExportException { 114 return configurationFormat; 115 } 116 117 @Nullable 118 @Override 119 public Format getOutputFormat() throws ExportException { 120 return outputFormat; 121 } 122 123 @Nullable 124 @Override 125 public ByteBuffer getOutputBuffer() throws ExportException { 126 { 127 PixelCopy.request(inputSurface, copyRect, drawBase, (int result) -> { 128 if (result != PixelCopy.SUCCESS) { 129 return; 130 } 131 Handler handle = new Handler(fileSaveThread.getLooper()) ; 132 handle.post(new Runnable() { 133 @Override 134 public void run() { 135 try { 136 File file = activity.getExternalFilesDir(null) ; 137 FileOutputStream tmpOut ; 138 try { 139 tmpOut = new FileOutputStream(file.getAbsolutePath()+String.format("/tmp%03d.jpg", no)) ; 140 drawBase.compress(Bitmap.CompressFormat.JPEG, 100, tmpOut) ; 141 tmpOut.close(); 142 no ++ ; 143 frameNo ++ ; 144 } catch(FileNotFoundException e) { 145 throw new RuntimeException(e); 146 } catch(IOException e) { 147 throw new RuntimeException(e); 148 } 149 } catch (Exception e) { 150 } 151 } 152 }) ; 153 }, new Handler(Looper.getMainLooper())); 154 155// int [] pixels = new int[1920*1080] ; 156// drawBase.getPixels(pixels, 0, 0, 0, 0, 1920, 1080); 157// if(pixels != null) { 158 159// } 160 } 161 162 ByteBuffer returnValue = outputBuffer ; 163 outputBuffer = null ; 164 return returnValue; 165 } 166 167 @Nullable 168 @Override 169 public MediaCodec.BufferInfo getOutputBufferInfo() throws ExportException { 170 MediaCodec.BufferInfo returnInfo = outputBufferInfo ; 171 long sampleTime = (long)((1000 / configurationFormat.frameRate) * frameNo) ; 172 173 outputBufferInfo.set(0, outputBufferInfo.size, sampleTime, 0); 174 return returnInfo; 175 } 176 177 @Override 178 public void releaseOutputBuffer(boolean render) throws ExportException { 179 outputBuffer = ByteBuffer.allocate(outputFormat.width*outputFormat.height*4*2) ; 180 } 181 182 @Override 183 public void releaseOutputBuffer(long renderPresentationTimeUs) throws ExportException { 184 outputBuffer = ByteBuffer.allocate(outputFormat.width*outputFormat.height*4*2) ; 185 } 186 187 @Override 188 public boolean isEnded() { 189 return false; 190 } 191 192 @Override 193 public void release() { 194 195 } 196} 197

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

PixelCopyの実行スレッドをoutputThreadにすると胴切れします。
メインスレッドでやると胴切れしませんがフレームドロップします。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

drednote

2025/06/30 07:48

すみません、ファイル書き込みスレッドを別けただけでも胴切れしてました
guest

回答1

0

自己解決

エンコーダCodecの実装で、getOutputBufferやreleaseOutputBufferの実装が色々と不味かったようでした。
基本的に内部のMediaCodecは非同期モードで動いており、胴切れを起こすのは単にバグの為です。
getOutputBufferでSurfaceに入っている映像を取り込んでいる時、上記ソースではgetOutputBuffer処理が終った後、Mainスレッドのルーパーがメッセージ処理を行う時に実行される事になるので基本的に別スレッドで行う必要があり、getOutputBuffer内ではPixelCopyの終了を待ってから処理を戻さなければなりません。
またreleaseOutputBufferdeでoutputBufferのallocateをするのは意味不明で、ここは寧ろoutputBufferをreleaseする必要があります。outputBufferはPixelCopyの完了時に確保します。
このようにすることでこの問題は解決しました。
お騒がせしました。

投稿2025/07/02 08:24

drednote

総合スコア345

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2025/07/02 16:51

折角なので解決した jpegMakeCodec.java を回答に載せては如何でしょうか。
drednote

2025/07/03 05:30

```Java / getOutputBufferで返すoutputBuffer は未設定です。ここは無視してください package com.tmptest.jpegmaketest; import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.Rect; import android.media.Image; import android.media.ImageReader; import android.media.MediaCodec; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.util.Log; import android.view.PixelCopy; import android.view.Surface; import androidx.annotation.Nullable; import androidx.media3.common.Format; import androidx.media3.common.util.UnstableApi; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.transformer.Codec; import androidx.media3.transformer.ExportException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; @UnstableApi public class jpegMakeCodec implements Codec { MainActivity activity ; Format configurationFormat ; Format outputFormat ; Surface inputSurface ; ByteBuffer outputBuffer ; MediaCodec.BufferInfo outputBufferInfo ; ImageReader iReader ; HandlerThread outputThread ; Looper applicationLooper ; long duration ; long presentationTime ; int no ; syncronizedObj syncObj ; Bitmap drawBase ; Rect copyRect ; public jpegMakeCodec(MainActivity useAct, Format configurationBaseFormat, Looper useLooper) { activity = useAct ; applicationLooper = useLooper ; configurationFormat = configurationBaseFormat ; outputFormat = configurationFormat.buildUpon() .setFrameRate(30) .build() ; iReader = ImageReader.newInstance(configurationBaseFormat.width, configurationFormat.height, ImageFormat.YUY2, 2) ; inputSurface = iReader.getSurface() ; outputBuffer = null ; outputBufferInfo = null ; outputThread = new HandlerThread("OutputThread") ; outputThread.start() ; syncObj = new syncronizedObj() ; drawBase = Bitmap.createBitmap(configurationFormat.width, configurationFormat.height, Bitmap.Config.ARGB_8888) ; copyRect = new Rect(0, 0, configurationFormat.width, configurationFormat.height) ; no = 0 ; presentationTime = 0 ; } public void setDurationMS(long useDuration) { duration = useDuration ; } @Override public Format getConfigurationFormat() { return configurationFormat; } @Override public String getName() { return "testEncoder"; } @Override public Surface getInputSurface() { return inputSurface; } @Override public boolean maybeDequeueInputBuffer(DecoderInputBuffer inputBuffer) throws ExportException { return false; } @Override public void queueInputBuffer(DecoderInputBuffer inputBuffer) throws ExportException { } @Override public int getMaxPendingFrameCount() { return 1 ; } @Override public void signalEndOfInputStream() throws ExportException { } @Override public Format getInputFormat() throws ExportException { return configurationFormat; } @Nullable @Override public Format getOutputFormat() throws ExportException { return outputFormat; } @Nullable @Override public ByteBuffer getOutputBuffer() throws ExportException { if(outputBuffer != null) { return outputBuffer ; } if(no == 0) { outputBuffer = ByteBuffer.allocate(10*1024) ; byte[] dmy = new byte[10*1024] ; outputBuffer.put(dmy) ; outputBufferInfo = new MediaCodec.BufferInfo() ; outputBufferInfo.set(0, outputBuffer.remaining(), 0, 0); no ++ ; return outputBuffer ; } while(true) { if(iReader.getMaxImages() == 0) { try { Thread.sleep(1); } catch(InterruptedException e) { throw new RuntimeException(e); } } else { break ; } } { PixelCopy.request(inputSurface, copyRect, drawBase, (int result) -> { if (result != PixelCopy.SUCCESS) { syncObj.notifyObj(); return; } try { File file = activity.getExternalFilesDir(null) ; FileOutputStream tmpOut ; try { presentationTime = (long)((1000000/outputFormat.frameRate)*(no-1)) ; tmpOut = new FileOutputStream(file.getAbsolutePath()+String.format("/tmp%03d.jpg", no)) ; drawBase.compress(Bitmap.CompressFormat.JPEG, 100, tmpOut) ; int arraySize = 10 * 1024 ; byte[] tmpByteAry = new byte[arraySize] ; outputBuffer = ByteBuffer.allocate(arraySize) ; outputBuffer.put(tmpByteAry) ; tmpOut.close(); outputBufferInfo = new MediaCodec.BufferInfo() ; outputBufferInfo.set(0, outputBuffer.remaining(), presentationTime, (presentationTime >= (duration*1000))?MediaCodec.BUFFER_FLAG_END_OF_STREAM:0); no ++ ; } catch(FileNotFoundException e) { throw new RuntimeException(e); } catch(IOException e) { throw new RuntimeException(e); } } catch (Exception e) { throw new RuntimeException(e); } syncObj.notifyObj(); }, new Handler(outputThread.getLooper())); syncObj.waitObj() ; } } return outputBuffer; } @Nullable @Override public MediaCodec.BufferInfo getOutputBufferInfo() throws ExportException { return outputBufferInfo; } @Override public void releaseOutputBuffer(boolean render) throws ExportException { outputBuffer = null ; } @Override public void releaseOutputBuffer(long renderPresentationTimeUs) throws ExportException { outputBuffer = null ; } @Override public boolean isEnded() { return false; } @Override public void release() { } } ``` 修正版を貼り付けてみました。 キモが、getOutputBufferでPixelCopyの終了を待つということで、同期方法は何でもいいんですがとにかく別スレッドで動かしているPixelCopyの終了を待ってから戻り値でnullではないoutputBufferを返すようにしてやれば上手く行くようになります。 無論、上記サンプルはエンコード時にJPEGファイルに落としてしまっていますが、実際に動画にする際はByteArrayOutputStream等を使ってメモリに吐き出し、OutputBufferとして返す必要がある訳ですが、今回は割愛です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問