Я работаю над реализацией, в которой хочу создать список воспроизведения DASH из данных Raw Camera2 в Android Java с помощью FFmpeg
Однако текущая реализация создает только три .m4s файла независимо от продолжительности записи. Моя цель — создать список воспроизведения с 1-секундными сегментами .m4s, но выходные данные включают только следующие файлы, а продолжительность видео не превышает 2 секунд:
- playlist.mpd
- init.m4s
- 1.m4s
- 2.m4s
Хотя временные файлы создаются должным образом, создание файлов .m4s прекращается после этих двух сегментов. Кроме того, сохраняются только последние 2 секунды записи, независимо от продолжительности записи.
Вывод FFmpeg показывает, что FFmpeg неоднократно перезаписывает ранее созданный список воспроизведения, что может объяснить, почему запись не превышает 2 секунд
Версия FFmpeg: 6.0
package rev.ca.rev_media_dash_camera2;
import android.app.Activity;
import android.content.Context;
import android.media.Image;
import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import com.arthenica.ffmpegkit.FFmpegKit;
import com.arthenica.ffmpegkit.ReturnCode;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RevCameraCapture {
private static final String REV_TAG = "RevCameraCapture";
private final Context revContext;
private final ExecutorService revExecutorService;
private final String revOutDirPath = "/storage/emulated/0/Documents/Owki/rev_web_rtc_live_chat_temp_files/_abc_rev_uploads_temp";
private boolean isRevRecording;
private File revTempFile;
private int revFrameCount = 0; // Counter for frames captured
public RevCameraCapture(Context revContext) {
this.revContext = revContext;
revInitDir(revOutDirPath);
revCheckOrCreatePlaylist();
revExecutorService = Executors.newSingleThreadExecutor();
}
private void revInitDir(String revDirPath) {
// Create a File object for the directory
File revNestedDir = new File(revDirPath);
// Check if the directory exists, if not, create it
if (!revNestedDir.exists()) {
boolean revResult = revNestedDir.mkdirs(); // mkdirs() creates the whole path
if (revResult) {
Log.e(REV_TAG, ">>> Directories created successfully.");
} else {
Log.e(REV_TAG, ">>> Failed to create directories.");
}
} else {
Log.e(REV_TAG, ">>> Directories already exist.");
}
}
private void revCheckOrCreatePlaylist() {
File revPlaylistFile = new File(revOutDirPath, "rev_playlist.mpd");
if (!revPlaylistFile.exists()) {
// Create an empty playlist if it doesn't exist
try {
FileOutputStream revFos = new FileOutputStream(revPlaylistFile);
revFos.write("".getBytes());
revFos.close();
} catch (IOException e) {
Log.e(REV_TAG, ">>> Error creating initial rev_playlist : ", e);
}
}
}
private void revStartFFmpegProcess() {
// Ensure revTempFile exists before processing
if (revTempFile == null || !revTempFile.exists()) {
Log.e(REV_TAG, ">>> Temporary file does not exist for FFmpeg processing.");
return;
}
// FFmpeg command to convert the temp file to DASH format and append to the existing rev_playlist
String ffmpegCommand = "-f rawvideo -pixel_format yuv420p -video_size 704x704 " + "-i " + revTempFile.getAbsolutePath() + " -c:v mpeg4 -b:v 1M " + "-f dash -seg_duration 1 -use_template 1 -use_timeline 1 " + "-init_seg_name 'init.m4s' -media_seg_name '$Number$.m4s' " + revOutDirPath + "/rev_playlist.mpd -loglevel debug";
FFmpegKit.executeAsync(ffmpegCommand, session -> {
ReturnCode returnCode = session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
// Optionally handle success, e.g., log or notify that the process completed successfully
} else {
Log.e(REV_TAG, ">>> FFmpeg process failed with return code : " + returnCode);
}
});
}
public void revStartCamera() {
isRevRecording = true;
ListenableFuture revCameraProviderFuture = ProcessCameraProvider.getInstance(revContext);
revCameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider revCameraProvider = revCameraProviderFuture.get();
revBindPreview(revCameraProvider);
revBindImageAnalysis(revCameraProvider);
} catch (ExecutionException | InterruptedException e) {
Log.e(REV_TAG, ">>> Failed to start camera : ", e);
}
}, ContextCompat.getMainExecutor(revContext));
}
private void revBindPreview(ProcessCameraProvider revCameraProvider) {
CameraSelector revCameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
PreviewView previewView = ((Activity) revContext).findViewById(R.id.previewView);
Preview preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
revCameraProvider.unbindAll();
revCameraProvider.bindToLifecycle((LifecycleOwner) revContext, revCameraSelector, preview);
}
private void revBindImageAnalysis(@NonNull ProcessCameraProvider revCameraProvider) {
ImageAnalysis revImageAnalysis = new ImageAnalysis.Builder().setTargetResolution(new Size(640, 480)) // Lower the resolution to reduce memory consumption
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
revImageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(revContext), this::revAnalyze);
CameraSelector revCameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
revCameraProvider.bindToLifecycle((LifecycleOwner) revContext, revCameraSelector, revImageAnalysis);
}
@androidx.annotation.OptIn(markerClass = androidx.camera.core.ExperimentalGetImage.class)
private void revAnalyze(@NonNull ImageProxy revImageProxy) {
try {
revProcessImageFrame(revImageProxy);
} catch (Exception e) {
Log.e(REV_TAG, ">>> Error processing revImage frame", e);
} finally {
revImageProxy.close(); // Always close the revImageProxy
}
}
@androidx.annotation.OptIn(markerClass = androidx.camera.core.ExperimentalGetImage.class)
private void revProcessImageFrame(@NonNull ImageProxy revImageProxy) {
Image revImage = revImageProxy.getImage();
if (revImage != null) {
byte[] revImageBytes = revConvertYUV420888ToByteArray(revImage);
revWriteFrameToTempFile(revImageBytes); // Write frame to a temporary file
}
revImageProxy.close(); // Close the ImageProxy to release the revImage buffer
}
private byte[] revConvertYUV420888ToByteArray(Image revImage) {
Image.Plane[] planes = revImage.getPlanes();
ByteBuffer revBufferY = planes[0].getBuffer();
ByteBuffer revBufferU = planes[1].getBuffer();
ByteBuffer revBufferV = planes[2].getBuffer();
int revWidth = revImage.getWidth();
int revHeight = revImage.getHeight();
int revSizeY = revWidth * revHeight;
int revSizeUV = (revWidth / 2) * (revHeight / 2); // U and V sizes are half the Y size
// Total size = Y + U + V
byte[] revData = new byte[revSizeY + 2 * revSizeUV];
// Copy Y plane
revBufferY.get(revData, 0, revSizeY);
// Copy U and V planes, accounting for row stride and pixel stride
int revOffset = revSizeY;
int revPixelStrideU = planes[1].getPixelStride();
int rowStrideU = planes[1].getRowStride();
int revPixelStrideV = planes[2].getPixelStride();
int rowStrideV = planes[2].getRowStride();
// Copy U plane
for (int row = 0; row < revHeight / 2; row++) {
for (int col = 0; col < revWidth / 2; col++) {
revData[revOffset++] = revBufferU.get(row * rowStrideU + col * revPixelStrideU);
}
}
// Copy V plane
for (int row = 0; row < revHeight / 2; row++) {
for (int col = 0; col < revWidth / 2; col++) {
revData[revOffset++] = revBufferV.get(row * rowStrideV + col * revPixelStrideV);
}
}
return revData;
}
private void revWriteFrameToTempFile(byte[] revImageBytes) {
revExecutorService.execute(() -> {
try {
// Create a new temp file for each segment if needed
if (revTempFile == null || revFrameCount == 0) {
revTempFile = File.createTempFile("vid_segment_", ".yuv", new File(revOutDirPath));
}
try (FileOutputStream revFos = new FileOutputStream(revTempFile, true)) {
revFos.write(revImageBytes);
}
revFrameCount++;
// Process after 60 frames (2 second for 30 fps)
if (revFrameCount >= 60 && isRevRecording) {
revStartFFmpegProcess(); // Process the segment with FFmpeg
revFrameCount = 0; // Reset the frame count
revTempFile = null; // Reset temp file for the next segment
}
} catch (IOException e) {
Log.e(REV_TAG, ">>> Error writing frame to temp file : ", e);
}
});
}
public void revStopCamera() {
isRevRecording = false;
if (revTempFile != null && revTempFile.exists()) {
revTempFile.delete(); // Clean up the temporary file
revTempFile = null; // Reset the temp file reference
}
}
}
package rev.ca.rev_media_dash_camera2;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private RevCameraCapture revCameraCapture;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
revCameraCapture = new RevCameraCapture(this);
}
@Override
protected void onStart() {
super.onStart();
try {
revCameraCapture.revStartCamera();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStop() {
super.onStop();
revCameraCapture.revStopCamera(); // Ensure camera is stopped when not in use
}
}
Подробнее здесь: https://stackoverflow.com/questions/790 ... g-playlist
Плейлист с перехватом FFmpeg ⇐ JAVA
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение