impement android.media.MediaCodec using libavcodec

The current implementation requires a VA-API driver and a Wayland
compositor with YUV-buffer support. GNOME supports YUV-buffers
since the recent version 45 release
This commit is contained in:
Julian Winkler
2023-10-08 16:09:27 +02:00
parent 23c0b006ef
commit b340032e9f
13 changed files with 834 additions and 13 deletions

View File

@@ -1,9 +1,150 @@
package android.media;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
import java.util.Queue;
import android.view.Surface;
public class MediaCodec {
private String codecName;
private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers;
private long[] inputBufferTimestamps;
private long native_codec;
private boolean outputFormatSet = false;
private MediaFormat mediaFormat;
private Queue<Integer> freeOutputBuffers;
private Queue<Integer> queuedInputBuffers;
private Queue<Integer> freeInputBuffers;
private MediaCodec(String codecName) {
this.codecName = codecName;
native_codec = native_constructor(codecName);
inputBuffers = new ByteBuffer[1];
inputBufferTimestamps = new long[inputBuffers.length];
freeInputBuffers = new ArrayDeque<>(inputBuffers.length);
queuedInputBuffers = new ArrayDeque<>(inputBuffers.length);
for (int i = 0; i < inputBuffers.length; i++) {
inputBuffers[i] = ByteBuffer.allocate(262144).order(ByteOrder.LITTLE_ENDIAN);
freeInputBuffers.add(i);
}
outputBuffers = new ByteBuffer[2];
freeOutputBuffers = new ArrayDeque<>(outputBuffers.length);
for (int i = 0; i < outputBuffers.length; i++) {
outputBuffers[i] = ByteBuffer.allocate(4096).order(ByteOrder.LITTLE_ENDIAN);
freeOutputBuffers.add(i);
}
}
public static MediaCodec createByCodecName(String codecName) {
return new MediaCodec(codecName);
}
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
System.out.println("MediaCodec.configure(" + format + ", " + surface + ", " + crypto + ", " + flags + "): codecName=" + codecName);
this.mediaFormat = format;
if ("aac".equals(codecName)) {
native_configure_audio(native_codec, format.getByteBuffer("csd-0"), format.getInteger("sample-rate"), format.getInteger("channel-count"));
} else if ("h264".equals(codecName)) {
native_configure_video(native_codec, format.getByteBuffer("csd-0"), format.getByteBuffer("csd-1"), surface);
}
}
public void start() {
System.out.println("MediaCodec.start(): codecName=" + codecName);
native_start(native_codec);
}
public ByteBuffer[] getInputBuffers() {
return inputBuffers;
}
public ByteBuffer[] getOutputBuffers() {
return outputBuffers;
}
public int dequeueOutputBuffer(BufferInfo info, long timeoutUs) {
if (!outputFormatSet) {
outputFormatSet = true;
return /*INFO_OUTPUT_FORMAT_CHANGED*/ -2;
}
Integer index = freeOutputBuffers.poll();
if (index == null) {
return /*INFO_TRY_AGAIN_LATER*/ -1;
}
outputBuffers[index].clear();
int ret = native_dequeueOutputBuffer(native_codec, outputBuffers[index], info);
if (ret == 0) {
ret = index;
} else {
freeOutputBuffers.add(index);
ret = /*INFO_TRY_AGAIN_LATER*/ -1;
}
return ret;
}
public void releaseOutputBuffer(int index, boolean render) {
native_releaseOutputBuffer(native_codec, outputBuffers[index], render);
freeOutputBuffers.add(index);
}
public MediaFormat getOutputFormat() {
return mediaFormat;
}
public void flush() {}
private void tryProcessInputBuffer() {
Integer index = queuedInputBuffers.peek();
if (index != null) {
int ret = native_queueInputBuffer(native_codec, inputBuffers[index], inputBufferTimestamps[index]);
if (ret == 0) {
queuedInputBuffers.remove(index);
freeInputBuffers.add(index);
}
}
}
public int dequeueInputBuffer(long timeoutUs) {
tryProcessInputBuffer();
Integer index = freeInputBuffers.poll();
if (index == null) {
return /*INFO_TRY_AGAIN_LATER*/ -1;
} else {
return index;
}
}
public void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags) {
inputBufferTimestamps[index] = presentationTimeUs;
queuedInputBuffers.add(index);
tryProcessInputBuffer();
}
public void setVideoScalingMode(int mode) {
System.out.println("MediaCodec.setVideoScalingMode(" + mode + "): codecName=" + codecName);
}
private native long native_constructor(String codecName);
private native void native_configure_audio(long codec, ByteBuffer extradata, int sampleRate, int channelCount);
private native void native_configure_video(long codec, ByteBuffer csd0, ByteBuffer csd1, Surface surface);
private native void native_start(long codec);
private native int native_queueInputBuffer(long codec, ByteBuffer buffer, long presentationTimeUs);
private native int native_dequeueOutputBuffer(long codec, ByteBuffer buffer, BufferInfo info);
private native void native_releaseOutputBuffer(long codec, ByteBuffer buffer, boolean render);
public static final class CryptoInfo {}
public static final class BufferInfo {}
public static final class BufferInfo {
public int size;
public int flags;
public int offset;
public long presentationTimeUs;
}
public static interface OnFrameRenderedListener {}
}

View File

@@ -0,0 +1,32 @@
package android.media;
public class MediaCodecInfo {
private String name;
private String mime;
public MediaCodecInfo(String name, String mime) {
this.name = name;
this.mime = mime;
}
public String getName() {
return name;
}
public boolean isEncoder() {
return false;
}
public String[] getSupportedTypes() {
return new String[] { mime };
}
public CodecCapabilities getCapabilitiesForType(String type) {
return null;
}
public static class CodecCapabilities {}
public static class CodecProfileLevel {}
}

View File

@@ -3,7 +3,18 @@ package android.media;
public class MediaCodecList {
public static int getCodecCount() {
return 0;
return 2;
}
public static MediaCodecInfo getCodecInfoAt(int index) {
switch (index) {
case 0:
return new MediaCodecInfo("aac", "audio/mp4a-latm");
case 1:
return new MediaCodecInfo("h264", "video/avc");
default:
return null;
}
}
}

View File

@@ -0,0 +1,4 @@
package android.media;
public class MediaCrypto {
}

View File

@@ -0,0 +1,42 @@
package android.media;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
public class MediaFormat {
private Map<String, Object> map = new HashMap<>();
public void setString(String key, String value) {
map.put(key, value);
}
public void setInteger(String key, int value) {
map.put(key, value);
}
public void setByteBuffer(String key, ByteBuffer value) {
map.put(key, value);
}
public void setFloat(String key, float value) {
map.put(key, value);
}
public ByteBuffer getByteBuffer(String name) {
return (ByteBuffer) map.get(name);
}
public int getInteger(String name) {
return (int) map.get(name);
}
public boolean containsKey(String name) {
return map.containsKey(name);
}
public String toString() {
return map.toString();
}
}

View File

@@ -201,8 +201,11 @@ hax_jar = jar('hax', [
'android/media/AudioManager.java',
'android/media/AudioTrack.java',
'android/media/MediaCodec.java',
'android/media/MediaCodecInfo.java',
'android/media/MediaCodecList.java',
'android/media/MediaCrypto.java',
'android/media/MediaDescription.java',
'android/media/MediaFormat.java',
'android/media/MediaMetadata.java',
'android/media/MediaPlayer.java',
'android/media/MediaRouter.java',