From 1120e1027315687b3cca0de024c0d59fce2793a4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 27 Feb 2017 09:42:03 -0800 Subject: [PATCH] Add GVR spatial audio rendering extension. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=148658631 --- extensions/gvr/src/main/AndroidManifest.xml | 16 ++ .../ext/gvr/GvrBufferProcessor.java | 170 ++++++++++++++++++ settings.gradle | 2 + 3 files changed, 188 insertions(+) create mode 100644 extensions/gvr/src/main/AndroidManifest.xml create mode 100644 extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrBufferProcessor.java diff --git a/extensions/gvr/src/main/AndroidManifest.xml b/extensions/gvr/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6706b2507e --- /dev/null +++ b/extensions/gvr/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrBufferProcessor.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrBufferProcessor.java new file mode 100644 index 0000000000..fa94539c31 --- /dev/null +++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrBufferProcessor.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.gvr; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.audio.BufferProcessor; +import com.google.vr.sdk.audio.GvrAudioSurround; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Buffer processor that uses {@code GvrAudioSurround} to provide binaural rendering of surround + * sound and ambisonic soundfields. + */ +public final class GvrBufferProcessor implements BufferProcessor { + + private static final int FRAMES_PER_OUTPUT_BUFFER = 1024; + private static final int OUTPUT_CHANNEL_COUNT = 2; + private static final int OUTPUT_FRAME_SIZE = OUTPUT_CHANNEL_COUNT * 2; // 16-bit stereo output. + + private int sampleRateHz; + private int channelCount; + private GvrAudioSurround gvrAudioSurround; + private ByteBuffer buffer; + private boolean inputEnded; + + private float w; + private float x; + private float y; + private float z; + + public GvrBufferProcessor() { + // Use the identity for the initial orientation. + w = 1f; + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; + } + + /** + * Updates the listener head orientation. May be called on any thread. See + * {@code GvrAudioSurround.updateNativeOrientation}. + */ + public synchronized void updateOrientation(float w, float x, float y, float z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + if (gvrAudioSurround != null) { + gvrAudioSurround.updateNativeOrientation(w, x, y, z); + } + } + + @Override + public synchronized boolean configure(int sampleRateHz, int channelCount, + @C.Encoding int encoding) throws UnhandledFormatException { + if (encoding != C.ENCODING_PCM_16BIT) { + maybeReleaseGvrAudioSurround(); + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + maybeReleaseGvrAudioSurround(); + int surroundFormat; + switch (channelCount) { + case 2: + surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO; + break; + case 4: + surroundFormat = GvrAudioSurround.SurroundFormat.FIRST_ORDER_AMBISONICS; + break; + case 6: + surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_FIVE_DOT_ONE; + break; + case 9: + surroundFormat = GvrAudioSurround.SurroundFormat.SECOND_ORDER_AMBISONICS; + break; + case 16: + surroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS; + break; + default: + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + gvrAudioSurround = new GvrAudioSurround(surroundFormat, sampleRateHz, channelCount, + FRAMES_PER_OUTPUT_BUFFER); + gvrAudioSurround.updateNativeOrientation(w, x, y, z); + if (buffer == null) { + buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE) + .order(ByteOrder.nativeOrder()); + } + return true; + } + + @Override + public boolean isActive() { + return gvrAudioSurround != null; + } + + @Override + public int getOutputChannelCount() { + return OUTPUT_CHANNEL_COUNT; + } + + @Override + public int getOutputEncoding() { + return C.ENCODING_PCM_16BIT; + } + + @Override + public void queueInput(ByteBuffer input) { + int position = input.position(); + int readBytes = gvrAudioSurround.addInput(input, position, input.limit() - position); + input.position(position + readBytes); + } + + @Override + public void queueEndOfStream() { + inputEnded = true; + gvrAudioSurround.triggerProcessing(); + } + + @Override + public ByteBuffer getOutput() { + int writtenBytes = gvrAudioSurround.getOutput(buffer, 0, buffer.capacity()); + buffer.position(0).limit(writtenBytes); + return buffer; + } + + @Override + public boolean isEnded() { + return inputEnded && gvrAudioSurround.getAvailableOutputSize() == 0; + } + + @Override + public void flush() { + gvrAudioSurround.flush(); + inputEnded = false; + } + + @Override + public synchronized void release() { + buffer = null; + maybeReleaseGvrAudioSurround(); + } + + private void maybeReleaseGvrAudioSurround() { + if (this.gvrAudioSurround != null) { + GvrAudioSurround gvrAudioSurround = this.gvrAudioSurround; + this.gvrAudioSurround = null; + gvrAudioSurround.release(); + } + } + +} diff --git a/settings.gradle b/settings.gradle index d2d9cc87ed..b69c134fc4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,6 +17,7 @@ include ':demo' include ':playbacktests' include ':extension-ffmpeg' include ':extension-flac' +include ':extension-gvr' include ':extension-okhttp' include ':extension-opus' include ':extension-vp9' @@ -25,6 +26,7 @@ include ':extension-vp9' project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac') +project(':extension-gvr').projectDir = new File(settingsDir, 'extensions/gvr') project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp') project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus') project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9')