blob: 8189fa9c5c34f32d3ed85ed61f4f4810b541decd [file] [log] [blame]
/*
* Copyright (C) 2013 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.android.accessorydisplay.sink;
import com.android.accessorydisplay.common.Protocol;
import com.android.accessorydisplay.common.Service;
import com.android.accessorydisplay.common.Transport;
import android.content.Context;
import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat;
import android.os.Handler;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.nio.ByteBuffer;
public class DisplaySinkService extends Service implements SurfaceHolder.Callback {
private final ByteBuffer mBuffer = ByteBuffer.allocate(12);
private final Handler mTransportHandler;
private final int mDensityDpi;
private SurfaceView mSurfaceView;
// These fields are guarded by the following lock.
// This is to ensure that the surface lifecycle is respected. Although decoding
// happens on the transport thread, we are not allowed to access the surface after
// it is destroyed by the UI thread so we need to stop the codec immediately.
private final Object mSurfaceAndCodecLock = new Object();
private Surface mSurface;
private int mSurfaceWidth;
private int mSurfaceHeight;
private MediaCodec mCodec;
private ByteBuffer[] mCodecInputBuffers;
private BufferInfo mCodecBufferInfo;
public DisplaySinkService(Context context, Transport transport, int densityDpi) {
super(context, transport, Protocol.DisplaySinkService.ID);
mTransportHandler = transport.getHandler();
mDensityDpi = densityDpi;
}
public void setSurfaceView(final SurfaceView surfaceView) {
if (mSurfaceView != surfaceView) {
final SurfaceView oldSurfaceView = mSurfaceView;
mSurfaceView = surfaceView;
if (oldSurfaceView != null) {
oldSurfaceView.post(new Runnable() {
@Override
public void run() {
final SurfaceHolder holder = oldSurfaceView.getHolder();
holder.removeCallback(DisplaySinkService.this);
updateSurfaceFromUi(null);
}
});
}
if (surfaceView != null) {
surfaceView.post(new Runnable() {
@Override
public void run() {
final SurfaceHolder holder = surfaceView.getHolder();
holder.addCallback(DisplaySinkService.this);
updateSurfaceFromUi(holder);
}
});
}
}
}
@Override
public void onMessageReceived(int service, int what, ByteBuffer content) {
switch (what) {
case Protocol.DisplaySinkService.MSG_QUERY: {
getLogger().log("Received MSG_QUERY.");
sendSinkStatus();
break;
}
case Protocol.DisplaySinkService.MSG_CONTENT: {
decode(content);
break;
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Ignore. Wait for surface changed event that follows.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
updateSurfaceFromUi(holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
updateSurfaceFromUi(null);
}
private void updateSurfaceFromUi(SurfaceHolder holder) {
Surface surface = null;
int width = 0, height = 0;
if (holder != null && !holder.isCreating()) {
surface = holder.getSurface();
if (surface.isValid()) {
final Rect frame = holder.getSurfaceFrame();
width = frame.width();
height = frame.height();
} else {
surface = null;
}
}
synchronized (mSurfaceAndCodecLock) {
if (mSurface == surface && mSurfaceWidth == width && mSurfaceHeight == height) {
return;
}
mSurface = surface;
mSurfaceWidth = width;
mSurfaceHeight = height;
if (mCodec != null) {
mCodec.stop();
mCodec = null;
mCodecInputBuffers = null;
mCodecBufferInfo = null;
}
if (mSurface != null) {
MediaFormat format = MediaFormat.createVideoFormat(
"video/avc", mSurfaceWidth, mSurfaceHeight);
try {
mCodec = MediaCodec.createDecoderByType("video/avc");
} catch (IOException e) {
throw new RuntimeException(
"failed to create video/avc decoder", e);
}
mCodec.configure(format, mSurface, null, 0);
mCodec.start();
mCodecBufferInfo = new BufferInfo();
}
mTransportHandler.post(new Runnable() {
@Override
public void run() {
sendSinkStatus();
}
});
}
}
private void decode(ByteBuffer content) {
if (content == null) {
return;
}
synchronized (mSurfaceAndCodecLock) {
if (mCodec == null) {
return;
}
while (content.hasRemaining()) {
if (!provideCodecInputLocked(content)) {
getLogger().log("Dropping content because there are no available buffers.");
return;
}
consumeCodecOutputLocked();
}
}
}
private boolean provideCodecInputLocked(ByteBuffer content) {
final int index = mCodec.dequeueInputBuffer(0);
if (index < 0) {
return false;
}
if (mCodecInputBuffers == null) {
mCodecInputBuffers = mCodec.getInputBuffers();
}
final ByteBuffer buffer = mCodecInputBuffers[index];
final int capacity = buffer.capacity();
buffer.clear();
if (content.remaining() <= capacity) {
buffer.put(content);
} else {
final int limit = content.limit();
content.limit(content.position() + capacity);
buffer.put(content);
content.limit(limit);
}
buffer.flip();
mCodec.queueInputBuffer(index, 0, buffer.limit(), 0, 0);
return true;
}
private void consumeCodecOutputLocked() {
for (;;) {
final int index = mCodec.dequeueOutputBuffer(mCodecBufferInfo, 0);
if (index >= 0) {
mCodec.releaseOutputBuffer(index, true /*render*/);
} else if (index != MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
&& index != MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
break;
}
}
}
private void sendSinkStatus() {
synchronized (mSurfaceAndCodecLock) {
if (mCodec != null) {
mBuffer.clear();
mBuffer.putInt(mSurfaceWidth);
mBuffer.putInt(mSurfaceHeight);
mBuffer.putInt(mDensityDpi);
mBuffer.flip();
getTransport().sendMessage(Protocol.DisplaySourceService.ID,
Protocol.DisplaySourceService.MSG_SINK_AVAILABLE, mBuffer);
} else {
getTransport().sendMessage(Protocol.DisplaySourceService.ID,
Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE, null);
}
}
}
}