| /* |
| * 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); |
| } |
| } |
| } |
| } |