diff options
Diffstat (limited to 'media')
165 files changed, 13696 insertions, 1494 deletions
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index eae03be..aa60d0a 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -591,7 +591,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_LAST_AUDIBLE, device, s, @@ -606,7 +606,7 @@ public class AudioService extends IAudioService.Stub { // to persist). Do not change volume if stream is muted. sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, - SENDMSG_NOOP, + SENDMSG_QUEUE, device, 0, streamState, @@ -746,7 +746,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_LAST_AUDIBLE, device, streamState, @@ -758,7 +758,7 @@ public class AudioService extends IAudioService.Stub { // to persist). sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, - SENDMSG_NOOP, + SENDMSG_QUEUE, device, 0, streamState, @@ -2035,20 +2035,11 @@ public class AudioService extends IAudioService.Stub { } public void readSettings() { - int index = Settings.System.getInt(mContentResolver, - mVolumeIndexSettingName, - AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); - - mIndex.clear(); - mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); - - index = Settings.System.getInt(mContentResolver, - mLastAudibleVolumeIndexSettingName, - (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); - mLastAudibleIndex.clear(); - mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + boolean checkSilentVolume = (mRingerMode == AudioManager.RINGER_MODE_NORMAL) && + isStreamAffectedByRingerMode(mStreamType); int remainingDevices = AudioSystem.DEVICE_OUT_ALL; + for (int i = 0; remainingDevices != 0; i++) { int device = (1 << i); if ((device & remainingDevices) == 0) { @@ -2057,17 +2048,58 @@ public class AudioService extends IAudioService.Stub { remainingDevices &= ~device; // retrieve current volume for device - String name = getSettingNameForDevice(false, device); - index = Settings.System.getInt(mContentResolver, name, -1); + String name = getSettingNameForDevice(false /* lastAudible */, device); + // if no volume stored for current stream and device, use default volume if default + // device, continue otherwise + int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? + AudioManager.DEFAULT_STREAM_VOLUME[mStreamType] : -1; + int index = Settings.System.getInt(mContentResolver, name, defaultIndex); if (index == -1) { continue; } - mIndex.put(device, getValidIndex(10 * index)); // retrieve last audible volume for device - name = getSettingNameForDevice(true, device); - index = Settings.System.getInt(mContentResolver, name, -1); - mLastAudibleIndex.put(device, getValidIndex(10 * index)); + name = getSettingNameForDevice(true /* lastAudible */, device); + // use stored last audible index if present, otherwise use current index if not 0 + // or default index + defaultIndex = (index > 0) ? + index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; + int lastAudibleIndex = Settings.System.getInt(mContentResolver, name, defaultIndex); + + // a last audible index of 0 is never stored, except on non-voice capable devices + // (e.g. tablets) for the music stream type, where the music stream volume can reach + // 0 without the device being in silent mode + if ((lastAudibleIndex == 0) && + (mVoiceCapable || + (STREAM_VOLUME_ALIAS[mStreamType] != AudioSystem.STREAM_MUSIC))) { + lastAudibleIndex = AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; + // Correct the data base + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_LAST_AUDIBLE, + device, + this, + PERSIST_DELAY); + } + mLastAudibleIndex.put(device, getValidIndex(10 * lastAudibleIndex)); + // the initial index should never be 0 for a stream affected by ringer mode if not + // in silent or vibrate mode. + // this is permitted on tablets for music stream type. + if (checkSilentVolume && (index == 0) && + (mVoiceCapable || + (STREAM_VOLUME_ALIAS[mStreamType] != AudioSystem.STREAM_MUSIC))) { + index = lastAudibleIndex; + // Correct the data base + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_CURRENT, + device, + this, + PERSIST_DELAY); + } + mIndex.put(device, getValidIndex(10 * index)); } } @@ -2208,7 +2240,7 @@ public class AudioService extends IAudioService.Stub { } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, - SENDMSG_NOOP, + SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); @@ -2252,7 +2284,7 @@ public class AudioService extends IAudioService.Stub { } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, - SENDMSG_NOOP, + SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); @@ -2350,7 +2382,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_CURRENT|PERSIST_LAST_AUDIBLE, device, streamState, diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index d354cdb..b5e832c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -279,6 +279,7 @@ public class AudioSystem return DEVICE_OUT_ANLG_DOCK_HEADSET_NAME; case DEVICE_OUT_DGTL_DOCK_HEADSET: return DEVICE_OUT_DGTL_DOCK_HEADSET_NAME; + case DEVICE_IN_DEFAULT: default: return ""; } diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java index e94bddc..511111c 100644 --- a/media/java/android/media/CamcorderProfile.java +++ b/media/java/android/media/CamcorderProfile.java @@ -67,6 +67,8 @@ public class CamcorderProfile /** * Quality level corresponding to the 480p (720 x 480) resolution. + * Note that the horizontal resolution for 480p can also be other + * values, such as 640 or 704, instead of 720. */ public static final int QUALITY_480P = 4; @@ -76,7 +78,10 @@ public class CamcorderProfile public static final int QUALITY_720P = 5; /** - * Quality level corresponding to the 1080p (1920 x 1088) resolution. + * Quality level corresponding to the 1080p (1920 x 1080) resolution. + * Note that the vertical resolution for 1080p can also be 1088, + * instead of 1080 (used by some vendors to avoid cropping during + * video playback). */ public static final int QUALITY_1080P = 6; diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java new file mode 100644 index 0000000..7a520fe --- /dev/null +++ b/media/java/android/media/MediaActionSound.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2012 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 android.media; + +import android.media.AudioManager; +import android.media.SoundPool; +import android.util.Log; + +/** + * <p>A class for producing sounds that match those produced by various actions + * taken by the media and camera APIs. </p> + * + * <p>Use this class to play an appropriate camera operation sound when + * implementing a custom still or video recording mechanism (through the Camera + * preview callbacks with {@link android.hardware.Camera#setPreviewCallback + * Camera.setPreviewCallback}, or through GPU processing with {@link + * android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for + * example), or when implementing some other camera-like function in your + * application.</p> + * + * <p>There is no need to play sounds when using + * {@link android.hardware.Camera#takePicture Camera.takePicture} or + * {@link android.media.MediaRecorder} for still images or video, respectively, + * as the Android framework will play the appropriate sounds when needed for + * these calls.</p> + * + */ +public class MediaActionSound { + private static final int NUM_MEDIA_SOUND_STREAMS = 1; + + private SoundPool mSoundPool; + private int[] mSoundIds; + private int mSoundIdToPlay; + + private static final String[] SOUND_FILES = { + "/system/media/audio/ui/camera_click.ogg", + "/system/media/audio/ui/camera_focus.ogg", + "/system/media/audio/ui/VideoRecord.ogg", + "/system/media/audio/ui/VideoRecord.ogg" + }; + + private static final String TAG = "MediaActionSound"; + /** + * The sound used by + * {@link android.hardware.Camera#takePicture Camera.takePicture} to + * indicate still image capture. + * @see #play + */ + public static final int SHUTTER_CLICK = 0; + + /** + * A sound to indicate that focusing has completed. Because deciding + * when this occurs is application-dependent, this sound is not used by + * any methods in the media or camera APIs. + * @see #play + */ + public static final int FOCUS_COMPLETE = 1; + + /** + * The sound used by + * {@link android.media.MediaRecorder#start MediaRecorder.start()} to + * indicate the start of video recording. + * @see #play + */ + public static final int START_VIDEO_RECORDING = 2; + + /** + * The sound used by + * {@link android.media.MediaRecorder#stop MediaRecorder.stop()} to + * indicate the end of video recording. + * @see #play + */ + public static final int STOP_VIDEO_RECORDING = 3; + + private static final int SOUND_NOT_LOADED = -1; + + /** + * Construct a new MediaActionSound instance. Only a single instance is + * needed for playing any platform media action sound; you do not need a + * separate instance for each sound type. + */ + public MediaActionSound() { + mSoundPool = new SoundPool(NUM_MEDIA_SOUND_STREAMS, + AudioManager.STREAM_SYSTEM_ENFORCED, 0); + mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener); + mSoundIds = new int[SOUND_FILES.length]; + for (int i = 0; i < mSoundIds.length; i++) { + mSoundIds[i] = SOUND_NOT_LOADED; + } + mSoundIdToPlay = SOUND_NOT_LOADED; + } + + /** + * Preload a predefined platform sound to minimize latency when the sound is + * played later by {@link #play}. + * @param soundName The type of sound to preload, selected from + * SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or + * STOP_VIDEO_RECORDING. + * @see #play + * @see #SHUTTER_CLICK + * @see #FOCUS_COMPLETE + * @see #START_VIDEO_RECORDING + * @see #STOP_VIDEO_RECORDING + */ + public synchronized void load(int soundName) { + if (soundName < 0 || soundName >= SOUND_FILES.length) { + throw new RuntimeException("Unknown sound requested: " + soundName); + } + if (mSoundIds[soundName] == SOUND_NOT_LOADED) { + mSoundIds[soundName] = + mSoundPool.load(SOUND_FILES[soundName], 1); + } + } + + /** + * <p>Play one of the predefined platform sounds for media actions.</p> + * + * <p>Use this method to play a platform-specific sound for various media + * actions. The sound playback is done asynchronously, with the same + * behavior and content as the sounds played by + * {@link android.hardware.Camera#takePicture Camera.takePicture}, + * {@link android.media.MediaRecorder#start MediaRecorder.start}, and + * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p> + * + * <p>Using this method makes it easy to match the default device sounds + * when recording or capturing data through the preview callbacks, or when + * implementing custom camera-like features in your + * application.</p> + * + * <p>If the sound has not been loaded by {@link #load} before calling play, + * play will load the sound at the cost of some additional latency before + * sound playback begins. </p> + * + * @param soundName The type of sound to play, selected from + * SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or + * STOP_VIDEO_RECORDING. + * @see android.hardware.Camera#takePicture + * @see android.media.MediaRecorder + * @see #SHUTTER_CLICK + * @see #FOCUS_COMPLETE + * @see #START_VIDEO_RECORDING + * @see #STOP_VIDEO_RECORDING + */ + public synchronized void play(int soundName) { + if (soundName < 0 || soundName >= SOUND_FILES.length) { + throw new RuntimeException("Unknown sound requested: " + soundName); + } + if (mSoundIds[soundName] == SOUND_NOT_LOADED) { + mSoundIdToPlay = + mSoundPool.load(SOUND_FILES[soundName], 1); + mSoundIds[soundName] = mSoundIdToPlay; + } else { + mSoundPool.play(mSoundIds[soundName], 1.0f, 1.0f, 0, 0, 1.0f); + } + } + + private SoundPool.OnLoadCompleteListener mLoadCompleteListener = + new SoundPool.OnLoadCompleteListener() { + public void onLoadComplete(SoundPool soundPool, + int sampleId, int status) { + if (status == 0) { + if (mSoundIdToPlay == sampleId) { + soundPool.play(sampleId, 1.0f, 1.0f, 0, 0, 1.0f); + mSoundIdToPlay = SOUND_NOT_LOADED; + } + } else { + Log.e(TAG, "Unable to load sound for playback (status: " + + status + ")"); + } + } + }; + + /** + * Free up all audio resources used by this MediaActionSound instance. Do + * not call any other methods on a MediaActionSound instance after calling + * release(). + */ + public void release() { + if (mSoundPool != null) { + mSoundPool.release(); + mSoundPool = null; + } + } +} diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java new file mode 100644 index 0000000..7f496ca --- /dev/null +++ b/media/java/android/media/MediaCodec.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2012 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 android.media; + +import android.view.Surface; +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * MediaCodec class can be used to access low-level media codec, i.e. + * encoder/decoder components. + * @hide +*/ +public class MediaCodec +{ + /** Per buffer metadata includes an offset and size specifying + the range of valid data in the associated codec buffer. + */ + public final static class BufferInfo { + public void set( + int offset, int size, long timeUs, int flags) { + mOffset = offset; + mSize = size; + mPresentationTimeUs = timeUs; + mFlags = flags; + } + + public int mOffset; + public int mSize; + public long mPresentationTimeUs; + public int mFlags; + }; + + public static int FLAG_SYNCFRAME = 1; + public static int FLAG_CODECCONFIG = 2; + public static int FLAG_EOS = 4; + + /** Instantiate a codec component by mime type. For decoder components + this is the mime type of media that this decoder should be able to + decoder, for encoder components it's the type of media this encoder + should encode _to_. + */ + public static MediaCodec CreateByType(String type, boolean encoder) { + return new MediaCodec(type, true /* nameIsType */, encoder); + } + + /** If you know the exact name of the component you want to instantiate + use this method to instantiate it. Use with caution. + */ + public static MediaCodec CreateByComponentName(String name) { + return new MediaCodec( + name, false /* nameIsType */, false /* unused */); + } + + private MediaCodec( + String name, boolean nameIsType, boolean encoder) { + native_setup(name, nameIsType, encoder); + } + + @Override + protected void finalize() { + native_finalize(); + } + + // Make sure you call this when you're done to free up any opened + // component instance instead of relying on the garbage collector + // to do this for you at some point in the future. + public native final void release(); + + public static int CONFIGURE_FLAG_ENCODE = 1; + + /** Configures a component. + * @param format A map of string/value pairs describing the input format + * (decoder) or the desired output format. + * + * Video formats have the following fields: + * "mime" - String + * "width" - Integer + * "height" - Integer + * optional "max-input-size" - Integer + * optional "csd-0", "csd-1" ... - ByteBuffer + * + * Audio formats have the following fields: + * "mime" - String + * "channel-count" - Integer + * "sample-rate" - Integer + * optional "max-input-size" - Integer + * optional "csd-0", "csd-1" ... - ByteBuffer + * + * If the format is used to configure an encoder, additional + * fields must be included: + * "bitrate" - Integer (in bits/sec) + * + * for video formats: + * "color-format" - Integer + * "frame-rate" - Integer or Float + * "i-frame-interval" - Integer + * optional "stride" - Integer, defaults to "width" + * optional "slice-height" - Integer, defaults to "height" + * + * @param surface Specify a surface on which to render the output of this + * decoder. + * @param flags Specify {@see #CONFIGURE_FLAG_ENCODE} to configure the + * component as an encoder. + */ + public void configure( + Map<String, Object> format, Surface surface, int flags) { + String[] keys = null; + Object[] values = null; + + if (format != null) { + keys = new String[format.size()]; + values = new Object[format.size()]; + + int i = 0; + for (Map.Entry<String, Object> entry: format.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + ++i; + } + } + + native_configure(keys, values, surface, flags); + } + + private native final void native_configure( + String[] keys, Object[] values, Surface surface, int flags); + + /** After successfully configuring the component, call start. On return + * you can query the component for its input/output buffers. + */ + public native final void start(); + + public native final void stop(); + + /** Flush both input and output ports of the component, all indices + * previously returned in calls to dequeueInputBuffer and + * dequeueOutputBuffer become invalid. + */ + public native final void flush(); + + /** After filling a range of the input buffer at the specified index + * submit it to the component. + */ + public native final void queueInputBuffer( + int index, + int offset, int size, long presentationTimeUs, int flags); + + // Returns the index of an input buffer to be filled with valid data + // or -1 if no such buffer is currently available. + // This method will return immediately if timeoutUs == 0, wait indefinitely + // for the availability of an input buffer if timeoutUs < 0 or wait up + // to "timeoutUs" microseconds if timeoutUs > 0. + public native final int dequeueInputBuffer(long timeoutUs); + + // Returns the index of an output buffer that has been successfully + // decoded or one of the INFO_* constants below. + // The provided "info" will be filled with buffer meta data. + public static final int INFO_TRY_AGAIN_LATER = -1; + public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; + public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; + + /** Dequeue an output buffer, block at most "timeoutUs" microseconds. */ + public native final int dequeueOutputBuffer( + BufferInfo info, long timeoutUs); + + // If you are done with a buffer, use this call to return the buffer to + // the codec. If you previously specified a surface when configuring this + // video decoder you can optionally render the buffer. + public native final void releaseOutputBuffer(int index, boolean render); + + /** Call this after dequeueOutputBuffer signals a format change by returning + * {@see #INFO_OUTPUT_FORMAT_CHANGED} + */ + public native final Map<String, Object> getOutputFormat(); + + /** Call this after start() returns and whenever dequeueOutputBuffer + * signals an output buffer change by returning + * {@see #INFO_OUTPUT_BUFFERS_CHANGED} + */ + public native final ByteBuffer[] getBuffers(boolean input); + + private static native final void native_init(); + + private native final void native_setup( + String name, boolean nameIsType, boolean encoder); + + private native final void native_finalize(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + + private int mNativeContext; +} diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java new file mode 100644 index 0000000..6a7f2f5 --- /dev/null +++ b/media/java/android/media/MediaExtractor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 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 android.media; + +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * MediaExtractor + * @hide +*/ +public class MediaExtractor +{ + public MediaExtractor(String path) { + native_setup(path); + } + + @Override + protected void finalize() { + native_finalize(); + } + + // Make sure you call this when you're done to free up any resources + // instead of relying on the garbage collector to do this for you at + // some point in the future. + public native final void release(); + + public native int countTracks(); + public native Map<String, Object> getTrackFormat(int index); + + // Subsequent calls to "readSampleData", "getSampleTrackIndex" and + // "getSampleTime" only retrieve information for the subset of tracks + // selected by the call below. + // Selecting the same track multiple times has no effect, the track + // is only selected once. + public native void selectTrack(int index); + + // All selected tracks seek near the requested time. The next sample + // returned for each selected track will be a sync sample. + public native void seekTo(long timeUs); + + public native boolean advance(); + + // Retrieve the current encoded sample and store it in the byte buffer + // starting at the given offset. + public native int readSampleData(ByteBuffer byteBuf, int offset); + + // Returns the track index the current sample originates from. + public native int getSampleTrackIndex(); + + // Returns the current sample's presentation time in microseconds. + public native long getSampleTime(); + + private static native final void native_init(); + private native final void native_setup(String path); + private native final void native_finalize(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + + private int mNativeContext; +} diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 4c70e9d..e663e91 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -35,6 +35,7 @@ import android.media.AudioManager; import java.io.FileDescriptor; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.Map; import java.util.Set; import java.lang.ref.WeakReference; @@ -1486,6 +1487,52 @@ public class MediaPlayer */ public native static int native_pullBatteryData(Parcel reply); + /** + * Sets the target UDP re-transmit endpoint for the low level player. + * Generally, the address portion of the endpoint is an IP multicast + * address, although a unicast address would be equally valid. When a valid + * retransmit endpoint has been set, the media player will not decode and + * render the media presentation locally. Instead, the player will attempt + * to re-multiplex its media data using the Android@Home RTP profile and + * re-transmit to the target endpoint. Receiver devices (which may be + * either the same as the transmitting device or different devices) may + * instantiate, prepare, and start a receiver player using a setDataSource + * URL of the form... + * + * aahRX://<multicastIP>:<port> + * + * to receive, decode and render the re-transmitted content. + * + * setRetransmitEndpoint may only be called before setDataSource has been + * called; while the player is in the Idle state. + * + * @param endpoint the address and UDP port of the re-transmission target or + * null if no re-transmission is to be performed. + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if the retransmit endpoint is supplied, + * but invalid. + * + * {@hide} pending API council + */ + public void setRetransmitEndpoint(InetSocketAddress endpoint) + throws IllegalStateException, IllegalArgumentException + { + String addrString = null; + int port = 0; + + if (null != endpoint) { + addrString = endpoint.getAddress().getHostAddress(); + port = endpoint.getPort(); + } + + int ret = native_setRetransmitEndpoint(addrString, port); + if (ret != 0) { + throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret); + } + } + + private native final int native_setRetransmitEndpoint(String addrString, int port); + @Override protected void finalize() { native_finalize(); } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 85d99c1..6319630 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -303,6 +303,8 @@ public class MediaRecorder /** * Uses the settings from a CamcorderProfile object for recording. This method should * be called after the video AND audio sources are set, and before setOutputFile(). + * If a time lapse CamcorderProfile is used, audio related source or recording + * parameters are ignored. * * @param profile the CamcorderProfile to use * @see android.media.CamcorderProfile @@ -315,8 +317,8 @@ public class MediaRecorder setVideoEncoder(profile.videoCodec); if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW && profile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_QVGA) { - // Enable time lapse. Also don't set audio for time lapse. - setParameter(String.format("time-lapse-enable=1")); + // Nothing needs to be done. Call to setCaptureRate() enables + // time lapse video recording. } else { setAudioEncodingBitRate(profile.audioBitRate); setAudioChannels(profile.audioChannels); @@ -327,7 +329,10 @@ public class MediaRecorder /** * Set video frame capture rate. This can be used to set a different video frame capture - * rate than the recorded video's playback rate. Currently this works only for time lapse mode. + * rate than the recorded video's playback rate. This method also sets the recording mode + * to time lapse. In time lapse video recording, only video is recorded. Audio related + * parameters are ignored when a time lapse recording session starts, if an application + * sets them. * * @param fps Rate at which frames should be captured in frames per second. * The fps can go as low as desired. However the fastest fps will be limited by the hardware. @@ -339,6 +344,9 @@ public class MediaRecorder * possible. */ public void setCaptureRate(double fps) { + // Make sure that time lapse is enabled when this method is called. + setParameter(String.format("time-lapse-enable=1")); + double timeBetweenFrameCapture = 1 / fps; int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture); setParameter(String.format("time-between-time-lapse-frame-capture=%d", diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 52d31c7..a08d6c3 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -62,6 +62,9 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Locale; +import libcore.io.ErrnoException; +import libcore.io.Libcore; + /** * Internal service helper that no-one should use directly. * @@ -348,20 +351,18 @@ public class MediaScanner private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); - private static class FileCacheEntry { + private static class FileEntry { long mRowId; String mPath; long mLastModified; int mFormat; - boolean mSeenInFileSystem; boolean mLastModifiedChanged; - FileCacheEntry(long rowId, String path, long lastModified, int format) { + FileEntry(long rowId, String path, long lastModified, int format) { mRowId = rowId; mPath = path; mLastModified = lastModified; mFormat = format; - mSeenInFileSystem = false; mLastModifiedChanged = false; } @@ -373,11 +374,7 @@ public class MediaScanner private MediaInserter mMediaInserter; - // hashes file path to FileCacheEntry. - // path should be lower case if mCaseInsensitivePaths is true - private LinkedHashMap<String, FileCacheEntry> mFileCache; - - private ArrayList<FileCacheEntry> mPlayLists; + private ArrayList<FileEntry> mPlayLists; private DrmManagerClient mDrmManagerClient = null; @@ -432,7 +429,7 @@ public class MediaScanner private int mWidth; private int mHeight; - public FileCacheEntry beginFile(String path, String mimeType, long lastModified, + public FileEntry beginFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { mMimeType = mimeType; mFileType = 0; @@ -465,11 +462,7 @@ public class MediaScanner } } - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); - } - FileCacheEntry entry = mFileCache.get(key); + FileEntry entry = makeEntryFor(path); // add some slack to avoid a rounding error long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0; boolean wasModified = delta > 1 || delta < -1; @@ -477,13 +470,11 @@ public class MediaScanner if (wasModified) { entry.mLastModified = lastModified; } else { - entry = new FileCacheEntry(0, path, lastModified, + entry = new FileEntry(0, path, lastModified, (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0)); - mFileCache.put(key, entry); } entry.mLastModifiedChanged = true; } - entry.mSeenInFileSystem = true; if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) { mPlayLists.add(entry); @@ -525,7 +516,7 @@ public class MediaScanner Uri result = null; // long t1 = System.currentTimeMillis(); try { - FileCacheEntry entry = beginFile(path, mimeType, lastModified, + FileEntry entry = beginFile(path, mimeType, lastModified, fileSize, isDirectory, noMedia); // rescan for metadata if file was modified since last scan if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { @@ -778,7 +769,7 @@ public class MediaScanner return map; } - private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, + private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music, boolean podcasts) throws RemoteException { // update database @@ -1028,55 +1019,94 @@ public class MediaScanner String where = null; String[] selectionArgs = null; - if (mFileCache == null) { - mFileCache = new LinkedHashMap<String, FileCacheEntry>(); - } else { - mFileCache.clear(); - } if (mPlayLists == null) { - mPlayLists = new ArrayList<FileCacheEntry>(); + mPlayLists = new ArrayList<FileEntry>(); } else { mPlayLists.clear(); } if (filePath != null) { // query for only one file - where = Files.FileColumns.DATA + "=?"; - selectionArgs = new String[] { filePath }; + where = MediaStore.Files.FileColumns._ID + ">?" + + " AND " + Files.FileColumns.DATA + "=?"; + selectionArgs = new String[] { "", filePath }; + } else { + where = MediaStore.Files.FileColumns._ID + ">?"; + selectionArgs = new String[] { "" }; } + // Tell the provider to not delete the file. + // If the file is truly gone the delete is unnecessary, and we want to avoid + // accidentally deleting files that are really there (this may happen if the + // filesystem is mounted and unmounted while the scanner is running). + Uri.Builder builder = mFilesUri.buildUpon(); + builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); + MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build()); + // Build the list of files from the content provider try { if (prescanFiles) { - // First read existing files from the files table + // First read existing files from the files table. + // Because we'll be deleting entries for missing files as we go, + // we need to query the database in small batches, to avoid problems + // with CursorWindow positioning. + long lastId = Long.MIN_VALUE; + Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build(); + mWasEmptyPriorToScan = true; + + while (true) { + selectionArgs[0] = "" + lastId; + if (c != null) { + c.close(); + c = null; + } + c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION, + where, selectionArgs, MediaStore.Files.FileColumns._ID, null); + if (c == null) { + break; + } - c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, - where, selectionArgs, null, null); + int num = c.getCount(); - if (c != null) { - mWasEmptyPriorToScan = c.getCount() == 0; + if (num == 0) { + break; + } + mWasEmptyPriorToScan = false; while (c.moveToNext()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX); int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + lastId = rowId; // Only consider entries with absolute path names. // This allows storing URIs in the database without the // media scanner removing them. if (path != null && path.startsWith("/")) { - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); + boolean exists = false; + try { + exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK); + } catch (ErrnoException e1) { + } + if (!exists && !MtpConstants.isAbstractObject(format)) { + // do not delete missing playlists, since they may have been + // modified by the user. + // The user can delete them in the media player instead. + // instead, clear the path and lastModified fields in the row + MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); + int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); + + if (!MediaFile.isPlayListFileType(fileType)) { + deleter.delete(rowId); + if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) { + deleter.flush(); + String parent = new File(path).getParent(); + mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null); + } + } } - - FileCacheEntry entry = new FileCacheEntry(rowId, path, - lastModified, format); - mFileCache.put(key, entry); } } - c.close(); - c = null; } } } @@ -1084,6 +1114,7 @@ public class MediaScanner if (c != null) { c.close(); } + deleter.flush(); } // compute original size of images @@ -1186,57 +1217,6 @@ public class MediaScanner } private void postscan(String[] directories) throws RemoteException { - Iterator<FileCacheEntry> iterator = mFileCache.values().iterator(); - - // Tell the provider to not delete the file. - // If the file is truly gone the delete is unnecessary, and we want to avoid - // accidentally deleting files that are really there (this may happen if the - // filesystem is mounted and unmounted while the scanner is running). - Uri.Builder builder = mFilesUri.buildUpon(); - builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); - MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build()); - - while (iterator.hasNext()) { - FileCacheEntry entry = iterator.next(); - String path = entry.mPath; - - // remove database entries for files that no longer exist. - boolean fileMissing = false; - - if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) { - if (inScanDirectory(path, directories)) { - // we didn't see this file in the scan directory. - fileMissing = true; - } else { - // the file actually a directory or other abstract object - // or is outside of our scan directory, - // so we need to check for file existence here. - File testFile = new File(path); - if (!testFile.exists()) { - fileMissing = true; - } - } - } - - if (fileMissing) { - // do not delete missing playlists, since they may have been modified by the user. - // the user can delete them in the media player instead. - // instead, clear the path and lastModified fields in the row - MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); - int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); - - if (!MediaFile.isPlayListFileType(fileType)) { - deleter.delete(entry.mRowId); - iterator.remove(); - if (entry.mPath.toLowerCase(Locale.US).endsWith("/.nomedia")) { - deleter.flush(); - File f = new File(path); - mMediaProvider.call(MediaStore.UNHIDE_CALL, f.getParent(), null); - } - } - } - } - deleter.flush(); // handle playlists last, after we know what media files are on the storage. if (mProcessPlaylists) { @@ -1248,7 +1228,6 @@ public class MediaScanner // allow GC to clean up mPlayLists = null; - mFileCache = null; mMediaProvider = null; } @@ -1422,11 +1401,7 @@ public class MediaScanner // build file cache so we can look up tracks in the playlist prescan(null, true); - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); - } - FileCacheEntry entry = mFileCache.get(key); + FileEntry entry = makeEntryFor(path); if (entry != null) { processPlayList(entry); } @@ -1445,6 +1420,37 @@ public class MediaScanner } } + FileEntry makeEntryFor(String path) { + String key = path; + String where; + String[] selectionArgs; + if (mCaseInsensitivePaths) { + where = Files.FileColumns.DATA + " LIKE ?"; + selectionArgs = new String[] { path }; + } else { + where = Files.FileColumns.DATA + "=?"; + selectionArgs = new String[] { path }; + } + + Cursor c = null; + try { + c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, + where, selectionArgs, null, null); + if (c.moveToNext()) { + long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); + int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); + long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + return new FileEntry(rowId, path, lastModified, format); + } + } catch (RemoteException e) { + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + // returns the number of matching file/directory names, starting from the right private int matchPaths(String path1, String path2) { int result = 0; @@ -1495,26 +1501,37 @@ public class MediaScanner //FIXME - should we look for "../" within the path? // best matching MediaFile for the play list entry - FileCacheEntry bestMatch = null; + FileEntry bestMatch = null; // number of rightmost file/directory names for bestMatch int bestMatchLength = 0; - Iterator<FileCacheEntry> iterator = mFileCache.values().iterator(); - while (iterator.hasNext()) { - FileCacheEntry cacheEntry = iterator.next(); - String path = cacheEntry.mPath; + Cursor c = null; + try { + c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, + null, null, null, null); + } catch (RemoteException e1) { + } - if (path.equalsIgnoreCase(entry)) { - bestMatch = cacheEntry; - break; // don't bother continuing search - } + if (c != null) { + while (c.moveToNext()) { + long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX); + int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); + long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + + if (path.equalsIgnoreCase(entry)) { + bestMatch = new FileEntry(rowId, path, lastModified, format); + break; // don't bother continuing search + } - int matchLength = matchPaths(path, entry); - if (matchLength > bestMatchLength) { - bestMatch = cacheEntry; - bestMatchLength = matchLength; + int matchLength = matchPaths(path, entry); + if (matchLength > bestMatchLength) { + bestMatch = new FileEntry(rowId, path, lastModified, format); + bestMatchLength = matchLength; + } } + c.close(); } if (bestMatch == null) { @@ -1524,7 +1541,7 @@ public class MediaScanner try { // check rowid is set. Rowid may be missing if it is inserted by bulkInsert(). if (bestMatch.mRowId == 0) { - Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION, + c = mMediaProvider.query(mAudioUri, ID_PROJECTION, MediaStore.Files.FileColumns.DATA + "=?", new String[] { bestMatch.mPath }, null, null); if (c != null) { @@ -1677,7 +1694,7 @@ public class MediaScanner } } - private void processPlayList(FileCacheEntry entry) throws RemoteException { + private void processPlayList(FileEntry entry) throws RemoteException { String path = entry.mPath; ContentValues values = new ContentValues(); int lastSlash = path.lastIndexOf('/'); @@ -1728,9 +1745,9 @@ public class MediaScanner } private void processPlayLists() throws RemoteException { - Iterator<FileCacheEntry> iterator = mPlayLists.iterator(); + Iterator<FileEntry> iterator = mPlayLists.iterator(); while (iterator.hasNext()) { - FileCacheEntry entry = iterator.next(); + FileEntry entry = iterator.next(); // only process playlist files if they are new or have been modified since the last scan if (entry.mLastModifiedChanged) { processPlayList(entry); diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index bcf7b89..91d0add 100755 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -84,6 +84,7 @@ public class Visualizer { // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp private static final int NATIVE_EVENT_PCM_CAPTURE = 0; private static final int NATIVE_EVENT_FFT_CAPTURE = 1; + private static final int NATIVE_EVENT_SERVER_DIED = 2; // Error codes: /** @@ -147,6 +148,10 @@ public class Visualizer { * PCM and FFT capture listener registered by client */ private OnDataCaptureListener mCaptureListener = null; + /** + * Server Died listener registered by client + */ + private OnServerDiedListener mServerDiedListener = null; // accessed by native methods private int mNativeVisualizer; @@ -396,6 +401,9 @@ public class Visualizer { public interface OnDataCaptureListener { /** * Method called when a new waveform capture is available. + * <p>Data in the waveform buffer is valid only within the scope of the callback. + * Applications which needs access to the waveform data after returning from the callback + * should make a copy of the data instead of holding a reference. * @param visualizer Visualizer object on which the listener is registered. * @param waveform array of bytes containing the waveform representation. * @param samplingRate sampling rate of the audio visualized. @@ -404,6 +412,9 @@ public class Visualizer { /** * Method called when a new frequency capture is available. + * <p>Data in the fft buffer is valid only within the scope of the callback. + * Applications which needs access to the fft data after returning from the callback + * should make a copy of the data instead of holding a reference. * @param visualizer Visualizer object on which the listener is registered. * @param fft array of bytes containing the frequency representation. * @param samplingRate sampling rate of the audio visualized. @@ -452,6 +463,43 @@ public class Visualizer { } /** + * @hide + * + * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that + * the connection to the native media server has been broken and that the Visualizer object will + * need to be released and re-created. + * The client application can implement this interface and register the listener with the + * {@link #setServerDiedListener(OnServerDiedListener)} method. + */ + public interface OnServerDiedListener { + /** + * @hide + * + * Method called when the native media server has died. + * <p>If the native media server encounters a fatal error and needs to restart, the binder + * connection from the {@link #Visualizer} to the media server will be broken. Data capture + * callbacks will stop happening, and client initiated calls to the {@link #Visualizer} + * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality, + * clients should {@link #release()} their old visualizer and create a new instance. + */ + void onServerDied(); + } + + /** + * @hide + * + * Registers an OnServerDiedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + * @return {@link #SUCCESS} in case of success, + */ + public int setServerDiedListener(OnServerDiedListener listener) { + synchronized (mListenerLock) { + mServerDiedListener = listener; + } + return SUCCESS; + } + + /** * Helper class to handle the forwarding of native events to the appropriate listeners */ private class NativeEventHandler extends Handler @@ -463,11 +511,7 @@ public class Visualizer { mVisualizer = v; } - @Override - public void handleMessage(Message msg) { - if (mVisualizer == null) { - return; - } + private void handleCaptureMessage(Message msg) { OnDataCaptureListener l = null; synchronized (mListenerLock) { l = mVisualizer.mCaptureListener; @@ -476,6 +520,7 @@ public class Visualizer { if (l != null) { byte[] data = (byte[])msg.obj; int samplingRate = msg.arg1; + switch(msg.what) { case NATIVE_EVENT_PCM_CAPTURE: l.onWaveFormDataCapture(mVisualizer, data, samplingRate); @@ -484,11 +529,41 @@ public class Visualizer { l.onFftDataCapture(mVisualizer, data, samplingRate); break; default: - Log.e(TAG,"Unknown native event: "+msg.what); + Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what); break; } } } + + private void handleServerDiedMessage(Message msg) { + OnServerDiedListener l = null; + synchronized (mListenerLock) { + l = mVisualizer.mServerDiedListener; + } + + if (l != null) + l.onServerDied(); + } + + @Override + public void handleMessage(Message msg) { + if (mVisualizer == null) { + return; + } + + switch(msg.what) { + case NATIVE_EVENT_PCM_CAPTURE: + case NATIVE_EVENT_FFT_CAPTURE: + handleCaptureMessage(msg); + break; + case NATIVE_EVENT_SERVER_DIED: + handleServerDiedMessage(msg); + break; + default: + Log.e(TAG,"Unknown native event: "+msg.what); + break; + } + } } //--------------------------------------------------------- diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 23cc0e2..070d2d9 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_MediaCodec.cpp \ + android_media_MediaExtractor.cpp \ android_media_MediaPlayer.cpp \ android_media_MediaRecorder.cpp \ android_media_MediaScanner.cpp \ @@ -25,6 +27,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libgui \ libstagefright \ + libstagefright_foundation \ libcamera_client \ libmtp \ libusbhost \ @@ -39,10 +42,12 @@ LOCAL_C_INCLUDES += \ external/tremor/Tremor \ frameworks/base/core/jni \ frameworks/base/media/libmedia \ + frameworks/base/media/libstagefright \ frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ frameworks/base/media/libstagefright/codecs/amrnb/common \ frameworks/base/media/libstagefright/codecs/amrnb/common/include \ frameworks/base/media/mtp \ + frameworks/base/include/media/stagefright/openmax \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp new file mode 100644 index 0000000..71e698f --- /dev/null +++ b/media/jni/android_media_MediaCodec.cpp @@ -0,0 +1,552 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaCodec-JNI" +#include <utils/Log.h> + +#include "android_media_MediaCodec.h" + +#include "android_media_Utils.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_view_Surface.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <gui/Surface.h> +#include <gui/SurfaceTextureClient.h> + +#include <media/stagefright/MediaCodec.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +// Keep these in sync with their equivalents in MediaCodec.java !!! +enum { + DEQUEUE_INFO_TRY_AGAIN_LATER = -1, + DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED = -2, + DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3, +}; + +struct fields_t { + jfieldID context; +}; + +static fields_t gFields; + +//////////////////////////////////////////////////////////////////////////////// + +JMediaCodec::JMediaCodec( + JNIEnv *env, jobject thiz, + const char *name, bool nameIsType, bool encoder) + : mClass(NULL), + mObject(NULL) { + jclass clazz = env->GetObjectClass(thiz); + CHECK(clazz != NULL); + + mClass = (jclass)env->NewGlobalRef(clazz); + mObject = env->NewWeakGlobalRef(thiz); + + mLooper = new ALooper; + mLooper->setName("MediaCodec_looper"); + + mLooper->start( + false, // runOnCallingThread + false, // canCallJava + PRIORITY_DEFAULT); + + if (nameIsType) { + mCodec = MediaCodec::CreateByType(mLooper, name, encoder); + } else { + mCodec = MediaCodec::CreateByComponentName(mLooper, name); + } +} + +status_t JMediaCodec::initCheck() const { + return mCodec != NULL ? OK : NO_INIT; +} + +JMediaCodec::~JMediaCodec() { + mCodec->release(); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + env->DeleteGlobalRef(mClass); + mClass = NULL; +} + +status_t JMediaCodec::configure( + const sp<AMessage> &format, + const sp<ISurfaceTexture> &surfaceTexture, + int flags) { + sp<SurfaceTextureClient> client; + if (surfaceTexture != NULL) { + client = new SurfaceTextureClient(surfaceTexture); + } + return mCodec->configure(format, client, flags); +} + +status_t JMediaCodec::start() { + return mCodec->start(); +} + +status_t JMediaCodec::stop() { + return mCodec->stop(); +} + +status_t JMediaCodec::flush() { + return mCodec->flush(); +} + +status_t JMediaCodec::queueInputBuffer( + size_t index, + size_t offset, size_t size, int64_t timeUs, uint32_t flags) { + return mCodec->queueInputBuffer(index, offset, size, timeUs, flags); +} + +status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { + return mCodec->dequeueInputBuffer(index, timeoutUs); +} + +status_t JMediaCodec::dequeueOutputBuffer( + JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs) { + size_t size, offset; + int64_t timeUs; + uint32_t flags; + status_t err; + if ((err = mCodec->dequeueOutputBuffer( + index, &size, &offset, &timeUs, &flags, timeoutUs)) != OK) { + return err; + } + + jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo"); + + jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V"); + env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags); + + return OK; +} + +status_t JMediaCodec::releaseOutputBuffer(size_t index, bool render) { + return render + ? mCodec->renderOutputBufferAndRelease(index) + : mCodec->releaseOutputBuffer(index); +} + +status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const { + sp<AMessage> msg; + status_t err; + if ((err = mCodec->getOutputFormat(&msg)) != OK) { + return err; + } + + return ConvertMessageToMap(env, msg, format); +} + +status_t JMediaCodec::getBuffers( + JNIEnv *env, bool input, jobjectArray *bufArray) const { + Vector<sp<ABuffer> > buffers; + + status_t err = + input + ? mCodec->getInputBuffers(&buffers) + : mCodec->getOutputBuffers(&buffers); + + if (err != OK) { + return err; + } + + jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer"); + + *bufArray = (jobjectArray)env->NewObjectArray( + buffers.size(), byteBufferClass, NULL); + + for (size_t i = 0; i < buffers.size(); ++i) { + const sp<ABuffer> &buffer = buffers.itemAt(i); + + jobject byteBuffer = + env->NewDirectByteBuffer( + buffer->base(), + buffer->capacity()); + + env->SetObjectArrayElement( + *bufArray, i, byteBuffer); + + env->DeleteLocalRef(byteBuffer); + byteBuffer = NULL; + } + + return OK; +} + +} // namespace android + +//////////////////////////////////////////////////////////////////////////////// + +using namespace android; + +static sp<JMediaCodec> setMediaCodec( + JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec) { + sp<JMediaCodec> old = (JMediaCodec *)env->GetIntField(thiz, gFields.context); + if (codec != NULL) { + codec->incStrong(thiz); + } + if (old != NULL) { + old->decStrong(thiz); + } + env->SetIntField(thiz, gFields.context, (int)codec.get()); + + return old; +} + +static sp<JMediaCodec> getMediaCodec(JNIEnv *env, jobject thiz) { + return (JMediaCodec *)env->GetIntField(thiz, gFields.context); +} + +static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) { + setMediaCodec(env, thiz, NULL); +} + +static jint throwExceptionAsNecessary(JNIEnv *env, status_t err) { + switch (err) { + case OK: + return 0; + + case -EAGAIN: + return DEQUEUE_INFO_TRY_AGAIN_LATER; + + case INFO_FORMAT_CHANGED: + return DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED; + + case INFO_OUTPUT_BUFFERS_CHANGED: + return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED; + + default: + { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + break; + } + } + + return 0; +} + +static void android_media_MediaCodec_native_configure( + JNIEnv *env, + jobject thiz, + jobjectArray keys, jobjectArray values, + jobject jsurface, + jint flags) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + sp<AMessage> format; + status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format); + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + sp<ISurfaceTexture> surfaceTexture; + if (jsurface != NULL) { + sp<Surface> surface(Surface_getSurface(env, jsurface)); + if (surface != NULL) { + surfaceTexture = surface->getSurfaceTexture(); + } else { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + "The surface has been released"); + return; + } + } + + err = codec->configure(format, surfaceTexture, flags); + + throwExceptionAsNecessary(env, err); +} + +static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) { + ALOGV("android_media_MediaCodec_start"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = codec->start(); + + throwExceptionAsNecessary(env, err); +} + +static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) { + ALOGV("android_media_MediaCodec_stop"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = codec->stop(); + + throwExceptionAsNecessary(env, err); +} + +static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) { + ALOGV("android_media_MediaCodec_flush"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = codec->flush(); + + throwExceptionAsNecessary(env, err); +} + +static void android_media_MediaCodec_queueInputBuffer( + JNIEnv *env, + jobject thiz, + jint index, + jint offset, + jint size, + jlong timestampUs, + jint flags) { + ALOGV("android_media_MediaCodec_queueInputBuffer"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = codec->queueInputBuffer( + index, offset, size, timestampUs, flags); + + throwExceptionAsNecessary(env, err); +} + +static jint android_media_MediaCodec_dequeueInputBuffer( + JNIEnv *env, jobject thiz, jlong timeoutUs) { + ALOGV("android_media_MediaCodec_dequeueInputBuffer"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return -1; + } + + size_t index; + status_t err = codec->dequeueInputBuffer(&index, timeoutUs); + + if (err == OK) { + return index; + } + + return throwExceptionAsNecessary(env, err); +} + +static jint android_media_MediaCodec_dequeueOutputBuffer( + JNIEnv *env, jobject thiz, jobject bufferInfo, jlong timeoutUs) { + ALOGV("android_media_MediaCodec_dequeueOutputBuffer"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + size_t index; + status_t err = codec->dequeueOutputBuffer( + env, bufferInfo, &index, timeoutUs); + + if (err == OK) { + return index; + } + + return throwExceptionAsNecessary(env, err); +} + +static void android_media_MediaCodec_releaseOutputBuffer( + JNIEnv *env, jobject thiz, jint index, jboolean render) { + ALOGV("android_media_MediaCodec_renderOutputBufferAndRelease"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = codec->releaseOutputBuffer(index, render); + + throwExceptionAsNecessary(env, err); +} + +static jobject android_media_MediaCodec_getOutputFormat( + JNIEnv *env, jobject thiz) { + ALOGV("android_media_MediaCodec_getOutputFormat"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobject format; + status_t err = codec->getOutputFormat(env, &format); + + if (err == OK) { + return format; + } + + throwExceptionAsNecessary(env, err); + + return NULL; +} + +static jobjectArray android_media_MediaCodec_getBuffers( + JNIEnv *env, jobject thiz, jboolean input) { + ALOGV("android_media_MediaCodec_getBuffers"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobjectArray buffers; + status_t err = codec->getBuffers(env, input, &buffers); + + if (err == OK) { + return buffers; + } + + throwExceptionAsNecessary(env, err); + + return NULL; +} + +static void android_media_MediaCodec_native_init(JNIEnv *env) { + jclass clazz = env->FindClass("android/media/MediaCodec"); + CHECK(clazz != NULL); + + gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + CHECK(gFields.context != NULL); +} + +static void android_media_MediaCodec_native_setup( + JNIEnv *env, jobject thiz, + jstring name, jboolean nameIsType, jboolean encoder) { + if (name == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *tmp = env->GetStringUTFChars(name, NULL); + + if (tmp == NULL) { + return; + } + + sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder); + + status_t err = codec->initCheck(); + + env->ReleaseStringUTFChars(name, tmp); + tmp = NULL; + + if (err != OK) { + jniThrowException( + env, + "java/io/IOException", + "Failed to allocate component instance"); + return; + } + + setMediaCodec(env,thiz, codec); +} + +static void android_media_MediaCodec_native_finalize( + JNIEnv *env, jobject thiz) { + android_media_MediaCodec_release(env, thiz); +} + +static JNINativeMethod gMethods[] = { + { "release", "()V", (void *)android_media_MediaCodec_release }, + + { "native_configure", + "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;I)V", + (void *)android_media_MediaCodec_native_configure }, + + { "start", "()V", (void *)android_media_MediaCodec_start }, + { "stop", "()V", (void *)android_media_MediaCodec_stop }, + { "flush", "()V", (void *)android_media_MediaCodec_flush }, + + { "queueInputBuffer", "(IIIJI)V", + (void *)android_media_MediaCodec_queueInputBuffer }, + + { "dequeueInputBuffer", "(J)I", + (void *)android_media_MediaCodec_dequeueInputBuffer }, + + { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", + (void *)android_media_MediaCodec_dequeueOutputBuffer }, + + { "releaseOutputBuffer", "(IZ)V", + (void *)android_media_MediaCodec_releaseOutputBuffer }, + + { "getOutputFormat", "()Ljava/util/Map;", + (void *)android_media_MediaCodec_getOutputFormat }, + + { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;", + (void *)android_media_MediaCodec_getBuffers }, + + { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, + + { "native_setup", "(Ljava/lang/String;ZZ)V", + (void *)android_media_MediaCodec_native_setup }, + + { "native_finalize", "()V", + (void *)android_media_MediaCodec_native_finalize }, +}; + +int register_android_media_MediaCodec(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodec", gMethods, NELEM(gMethods)); +} diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h new file mode 100644 index 0000000..6b1257d --- /dev/null +++ b/media/jni/android_media_MediaCodec.h @@ -0,0 +1,81 @@ +/* + * Copyright 2012, 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. + */ + +#ifndef _ANDROID_MEDIA_MEDIACODEC_H_ +#define _ANDROID_MEDIA_MEDIACODEC_H_ + +#include "jni.h" + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +struct ALooper; +struct AMessage; +struct ISurfaceTexture; +struct MediaCodec; + +struct JMediaCodec : public RefBase { + JMediaCodec( + JNIEnv *env, jobject thiz, + const char *name, bool nameIsType, bool encoder); + + status_t initCheck() const; + + status_t configure( + const sp<AMessage> &format, + const sp<ISurfaceTexture> &surfaceTexture, + int flags); + + status_t start(); + status_t stop(); + + status_t flush(); + + status_t queueInputBuffer( + size_t index, + size_t offset, size_t size, int64_t timeUs, uint32_t flags); + + status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs); + + status_t dequeueOutputBuffer( + JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs); + + status_t releaseOutputBuffer(size_t index, bool render); + + status_t getOutputFormat(JNIEnv *env, jobject *format) const; + + status_t getBuffers( + JNIEnv *env, bool input, jobjectArray *bufArray) const; + +protected: + virtual ~JMediaCodec(); + +private: + jclass mClass; + jweak mObject; + + sp<ALooper> mLooper; + sp<MediaCodec> mCodec; + + DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIACODEC_H_ diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp new file mode 100644 index 0000000..4757adf --- /dev/null +++ b/media/jni/android_media_MediaExtractor.cpp @@ -0,0 +1,400 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaExtractor-JNI" +#include <utils/Log.h> + +#include "android_media_MediaExtractor.h" + +#include "android_media_Utils.h" +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/NuMediaExtractor.h> + +namespace android { + +struct fields_t { + jfieldID context; +}; + +static fields_t gFields; + +//////////////////////////////////////////////////////////////////////////////// + +JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz) + : mClass(NULL), + mObject(NULL) { + jclass clazz = env->GetObjectClass(thiz); + CHECK(clazz != NULL); + + mClass = (jclass)env->NewGlobalRef(clazz); + mObject = env->NewWeakGlobalRef(thiz); + + mImpl = new NuMediaExtractor; +} + +JMediaExtractor::~JMediaExtractor() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + env->DeleteGlobalRef(mClass); + mClass = NULL; +} + +status_t JMediaExtractor::setDataSource(const char *path) { + return mImpl->setDataSource(path); +} + +size_t JMediaExtractor::countTracks() const { + return mImpl->countTracks(); +} + +status_t JMediaExtractor::getTrackFormat(size_t index, jobject *format) const { + sp<AMessage> msg; + status_t err; + if ((err = mImpl->getTrackFormat(index, &msg)) != OK) { + return err; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + return ConvertMessageToMap(env, msg, format); +} + +status_t JMediaExtractor::selectTrack(size_t index) { + return mImpl->selectTrack(index); +} + +status_t JMediaExtractor::seekTo(int64_t timeUs) { + return mImpl->seekTo(timeUs); +} + +status_t JMediaExtractor::advance() { + return mImpl->advance(); +} + +status_t JMediaExtractor::readSampleData( + jobject byteBuf, size_t offset, size_t *sampleSize) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + void *dst = env->GetDirectBufferAddress(byteBuf); + + if (dst == NULL) { + // XXX if dst is NULL also fall back to "array()" + return INVALID_OPERATION; + } + + jlong dstSize = env->GetDirectBufferCapacity(byteBuf); + + if (dstSize < offset) { + return -ERANGE; + } + + sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset); + + status_t err = mImpl->readSampleData(buffer); + + if (err != OK) { + return err; + } + + *sampleSize = buffer->size(); + + return OK; +} + +status_t JMediaExtractor::getSampleTrackIndex(size_t *trackIndex) { + return mImpl->getSampleTrackIndex(trackIndex); +} + +status_t JMediaExtractor::getSampleTime(int64_t *sampleTimeUs) { + return mImpl->getSampleTime(sampleTimeUs); +} + +} // namespace android + +//////////////////////////////////////////////////////////////////////////////// + +using namespace android; + +static sp<JMediaExtractor> setMediaExtractor( + JNIEnv *env, jobject thiz, const sp<JMediaExtractor> &extractor) { + sp<JMediaExtractor> old = + (JMediaExtractor *)env->GetIntField(thiz, gFields.context); + + if (extractor != NULL) { + extractor->incStrong(thiz); + } + if (old != NULL) { + old->decStrong(thiz); + } + env->SetIntField(thiz, gFields.context, (int)extractor.get()); + + return old; +} + +static sp<JMediaExtractor> getMediaExtractor(JNIEnv *env, jobject thiz) { + return (JMediaExtractor *)env->GetIntField(thiz, gFields.context); +} + +static void android_media_MediaExtractor_release(JNIEnv *env, jobject thiz) { + setMediaExtractor(env, thiz, NULL); +} + +static jint android_media_MediaExtractor_countTracks( + JNIEnv *env, jobject thiz) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + return extractor->countTracks(); +} + +static jobject android_media_MediaExtractor_getTrackFormat( + JNIEnv *env, jobject thiz, jint index) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobject format; + status_t err = extractor->getTrackFormat(index, &format); + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + return format; +} + +static void android_media_MediaExtractor_selectTrack( + JNIEnv *env, jobject thiz, jint index) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = extractor->selectTrack(index); + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } +} + +static void android_media_MediaExtractor_seekTo( + JNIEnv *env, jobject thiz, jlong timeUs) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = extractor->seekTo(timeUs); + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } +} + +static jboolean android_media_MediaExtractor_advance( + JNIEnv *env, jobject thiz) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + + status_t err = extractor->advance(); + + if (err == ERROR_END_OF_STREAM) { + return false; + } else if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + return true; +} + +static jint android_media_MediaExtractor_readSampleData( + JNIEnv *env, jobject thiz, jobject byteBuf, jint offset) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return -1; + } + + size_t sampleSize; + status_t err = extractor->readSampleData(byteBuf, offset, &sampleSize); + + if (err == ERROR_END_OF_STREAM) { + return -1; + } else if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + return sampleSize; +} + +static jint android_media_MediaExtractor_getSampleTrackIndex( + JNIEnv *env, jobject thiz) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return -1; + } + + size_t trackIndex; + status_t err = extractor->getSampleTrackIndex(&trackIndex); + + if (err == ERROR_END_OF_STREAM) { + return -1; + } else if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + return trackIndex; +} + +static jlong android_media_MediaExtractor_getSampleTime( + JNIEnv *env, jobject thiz) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return -1ll; + } + + int64_t sampleTimeUs; + status_t err = extractor->getSampleTime(&sampleTimeUs); + + if (err == ERROR_END_OF_STREAM) { + return -1ll; + } else if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + return sampleTimeUs; +} + +static void android_media_MediaExtractor_native_init(JNIEnv *env) { + jclass clazz = env->FindClass("android/media/MediaExtractor"); + CHECK(clazz != NULL); + + gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + CHECK(gFields.context != NULL); + + DataSource::RegisterDefaultSniffers(); +} + +static void android_media_MediaExtractor_native_setup( + JNIEnv *env, jobject thiz, jstring path) { + sp<JMediaExtractor> extractor = new JMediaExtractor(env, thiz); + + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *tmp = env->GetStringUTFChars(path, NULL); + + if (tmp == NULL) { + return; + } + + status_t err = extractor->setDataSource(tmp); + + env->ReleaseStringUTFChars(path, tmp); + tmp = NULL; + + if (err != OK) { + jniThrowException( + env, + "java/io/IOException", + "Failed to instantiate extractor."); + return; + } + + setMediaExtractor(env,thiz, extractor); +} + +static void android_media_MediaExtractor_native_finalize( + JNIEnv *env, jobject thiz) { + android_media_MediaExtractor_release(env, thiz); +} + +static JNINativeMethod gMethods[] = { + { "release", "()V", (void *)android_media_MediaExtractor_release }, + + { "countTracks", "()I", (void *)android_media_MediaExtractor_countTracks }, + + { "getTrackFormat", "(I)Ljava/util/Map;", + (void *)android_media_MediaExtractor_getTrackFormat }, + + { "selectTrack", "(I)V", (void *)android_media_MediaExtractor_selectTrack }, + + { "seekTo", "(J)V", (void *)android_media_MediaExtractor_seekTo }, + + { "advance", "()Z", (void *)android_media_MediaExtractor_advance }, + + { "readSampleData", "(Ljava/nio/ByteBuffer;I)I", + (void *)android_media_MediaExtractor_readSampleData }, + + { "getSampleTrackIndex", "()I", + (void *)android_media_MediaExtractor_getSampleTrackIndex }, + + { "getSampleTime", "()J", + (void *)android_media_MediaExtractor_getSampleTime }, + + { "native_init", "()V", (void *)android_media_MediaExtractor_native_init }, + + { "native_setup", "(Ljava/lang/String;)V", + (void *)android_media_MediaExtractor_native_setup }, + + { "native_finalize", "()V", + (void *)android_media_MediaExtractor_native_finalize }, +}; + +int register_android_media_MediaExtractor(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaExtractor", gMethods, NELEM(gMethods)); +} diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h new file mode 100644 index 0000000..70e58c6 --- /dev/null +++ b/media/jni/android_media_MediaExtractor.h @@ -0,0 +1,60 @@ +/* + * Copyright 2012, 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. + */ + +#ifndef _ANDROID_MEDIA_MEDIAEXTRACTOR_H_ +#define _ANDROID_MEDIA_MEDIAEXTRACTOR_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +#include "jni.h" + +namespace android { + +struct NuMediaExtractor; + +struct JMediaExtractor : public RefBase { + JMediaExtractor(JNIEnv *env, jobject thiz); + + status_t setDataSource(const char *path); + + size_t countTracks() const; + status_t getTrackFormat(size_t index, jobject *format) const; + + status_t selectTrack(size_t index); + + status_t seekTo(int64_t timeUs); + + status_t advance(); + status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize); + status_t getSampleTrackIndex(size_t *trackIndex); + status_t getSampleTime(int64_t *sampleTimeUs); + +protected: + virtual ~JMediaExtractor(); + +private: + jclass mClass; + jweak mObject; + sp<NuMediaExtractor> mImpl; + + DISALLOW_EVIL_CONSTRUCTORS(JMediaExtractor); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIAEXTRACTOR_H_ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 8ff9dd3..6ec5d20 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -39,7 +39,7 @@ #include "android_util_Binder.h" #include <binder/Parcel.h> #include <gui/ISurfaceTexture.h> -#include <surfaceflinger/Surface.h> +#include <gui/Surface.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> @@ -722,6 +722,45 @@ android_media_MediaPlayer_pullBatteryData(JNIEnv *env, jobject thiz, jobject jav return service->pullBatteryData(reply); } +static jint +android_media_MediaPlayer_setRetransmitEndpoint(JNIEnv *env, jobject thiz, + jstring addrString, jint port) { + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return INVALID_OPERATION; + } + + const char *cAddrString = NULL; + + if (NULL != addrString) { + cAddrString = env->GetStringUTFChars(addrString, NULL); + if (cAddrString == NULL) { // Out of memory + return NO_MEMORY; + } + } + ALOGV("setRetransmitEndpoint: %s:%d", + cAddrString ? cAddrString : "(null)", port); + + status_t ret; + if (cAddrString && (port > 0xFFFF)) { + ret = BAD_VALUE; + } else { + ret = mp->setRetransmitEndpoint(cAddrString, + static_cast<uint16_t>(port)); + } + + if (NULL != addrString) { + env->ReleaseStringUTFChars(addrString, cAddrString); + } + + if (ret == INVALID_OPERATION ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } + + return ret; +} + static jboolean android_media_MediaPlayer_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request) { @@ -799,6 +838,7 @@ static JNINativeMethod gMethods[] = { {"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData}, {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter}, {"getParameter", "(ILandroid/os/Parcel;)V", (void *)android_media_MediaPlayer_getParameter}, + {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint}, }; static const char* const kClassPathName = "android/media/MediaPlayer"; @@ -810,6 +850,8 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_MediaCodec(JNIEnv *env); +extern int register_android_media_MediaExtractor(JNIEnv *env); extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaScanner(JNIEnv *env); @@ -881,6 +923,16 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_MediaCodec(env) < 0) { + ALOGE("ERROR: MediaCodec native registration failed"); + goto bail; + } + + if (register_android_media_MediaExtractor(env) < 0) { + ALOGE("ERROR: MediaCodec native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index acc65f1..b6e6ceb 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -18,7 +18,7 @@ #define LOG_TAG "MediaRecorderJNI" #include <utils/Log.h> -#include <surfaceflinger/SurfaceComposerClient.h> +#include <gui/Surface.h> #include <camera/ICameraService.h> #include <camera/Camera.h> #include <media/mediarecorder.h> diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index 47963b1..8b2321c 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -20,6 +20,10 @@ #include <utils/Log.h> #include "android_media_Utils.h" +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/AMessage.h> + namespace android { bool ConvertKeyValueArraysToKeyedVector( @@ -71,5 +75,263 @@ bool ConvertKeyValueArraysToKeyedVector( return true; } +static jobject makeIntegerObject(JNIEnv *env, int32_t value) { + jclass clazz = env->FindClass("java/lang/Integer"); + CHECK(clazz != NULL); + + jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V"); + CHECK(integerConstructID != NULL); + + return env->NewObject(clazz, integerConstructID, value); +} + +static jobject makeFloatObject(JNIEnv *env, float value) { + jclass clazz = env->FindClass("java/lang/Float"); + CHECK(clazz != NULL); + + jmethodID floatConstructID = env->GetMethodID(clazz, "<init>", "(F)V"); + CHECK(floatConstructID != NULL); + + return env->NewObject(clazz, floatConstructID, value); +} + +static jobject makeByteBufferObject( + JNIEnv *env, const void *data, size_t size) { + jbyteArray byteArrayObj = env->NewByteArray(size); + env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data); + + jclass clazz = env->FindClass("java/nio/ByteBuffer"); + CHECK(clazz != NULL); + + jmethodID byteBufWrapID = + env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;"); + CHECK(byteBufWrapID != NULL); + + jobject byteBufObj = env->CallStaticObjectMethod( + clazz, byteBufWrapID, byteArrayObj); + + env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL; + + return byteBufObj; +} + +status_t ConvertMessageToMap( + JNIEnv *env, const sp<AMessage> &msg, jobject *map) { + jclass hashMapClazz = env->FindClass("java/util/HashMap"); + + if (hashMapClazz == NULL) { + return -EINVAL; + } + + jmethodID hashMapConstructID = + env->GetMethodID(hashMapClazz, "<init>", "()V"); + + if (hashMapConstructID == NULL) { + return -EINVAL; + } + + jmethodID hashMapPutID = + env->GetMethodID( + hashMapClazz, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + if (hashMapPutID == NULL) { + return -EINVAL; + } + + jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID); + + for (size_t i = 0; i < msg->countEntries(); ++i) { + AMessage::Type valueType; + const char *key = msg->getEntryNameAt(i, &valueType); + + jobject valueObj = NULL; + + switch (valueType) { + case AMessage::kTypeInt32: + { + int32_t val; + CHECK(msg->findInt32(key, &val)); + + valueObj = makeIntegerObject(env, val); + break; + } + + case AMessage::kTypeFloat: + { + float val; + CHECK(msg->findFloat(key, &val)); + + valueObj = makeFloatObject(env, val); + break; + } + + case AMessage::kTypeString: + { + AString val; + CHECK(msg->findString(key, &val)); + + valueObj = env->NewStringUTF(val.c_str()); + break; + } + + case AMessage::kTypeBuffer: + { + sp<ABuffer> buffer; + CHECK(msg->findBuffer(key, &buffer)); + + valueObj = makeByteBufferObject( + env, buffer->data(), buffer->size()); + break; + } + + default: + break; + } + + if (valueObj != NULL) { + jstring keyObj = env->NewStringUTF(key); + + jobject res = env->CallObjectMethod( + hashMap, hashMapPutID, keyObj, valueObj); + + env->DeleteLocalRef(keyObj); keyObj = NULL; + env->DeleteLocalRef(valueObj); valueObj = NULL; + } + } + + *map = hashMap; + + return OK; +} + +status_t ConvertKeyValueArraysToMessage( + JNIEnv *env, jobjectArray keys, jobjectArray values, + sp<AMessage> *out) { + jclass stringClass = env->FindClass("java/lang/String"); + CHECK(stringClass != NULL); + + jclass integerClass = env->FindClass("java/lang/Integer"); + CHECK(integerClass != NULL); + + jclass floatClass = env->FindClass("java/lang/Float"); + CHECK(floatClass != NULL); + + jclass byteBufClass = env->FindClass("java/nio/ByteBuffer"); + CHECK(byteBufClass != NULL); + + sp<AMessage> msg = new AMessage; + + jsize numEntries = 0; + + if (keys != NULL) { + if (values == NULL) { + return -EINVAL; + } + + numEntries = env->GetArrayLength(keys); + + if (numEntries != env->GetArrayLength(values)) { + return -EINVAL; + } + } else if (values != NULL) { + return -EINVAL; + } + + for (jsize i = 0; i < numEntries; ++i) { + jobject keyObj = env->GetObjectArrayElement(keys, i); + + if (!env->IsInstanceOf(keyObj, stringClass)) { + return -EINVAL; + } + + const char *tmp = env->GetStringUTFChars((jstring)keyObj, NULL); + + if (tmp == NULL) { + return -ENOMEM; + } + + AString key = tmp; + + env->ReleaseStringUTFChars((jstring)keyObj, tmp); + tmp = NULL; + + jobject valueObj = env->GetObjectArrayElement(values, i); + + if (env->IsInstanceOf(valueObj, stringClass)) { + const char *value = env->GetStringUTFChars((jstring)valueObj, NULL); + + if (value == NULL) { + return -ENOMEM; + } + + msg->setString(key.c_str(), value); + + env->ReleaseStringUTFChars((jstring)valueObj, value); + value = NULL; + } else if (env->IsInstanceOf(valueObj, integerClass)) { + jmethodID intValueID = + env->GetMethodID(integerClass, "intValue", "()I"); + CHECK(intValueID != NULL); + + jint value = env->CallIntMethod(valueObj, intValueID); + + msg->setInt32(key.c_str(), value); + } else if (env->IsInstanceOf(valueObj, floatClass)) { + jmethodID floatValueID = + env->GetMethodID(floatClass, "floatValue", "()F"); + CHECK(floatValueID != NULL); + + jfloat value = env->CallFloatMethod(valueObj, floatValueID); + + msg->setFloat(key.c_str(), value); + } else if (env->IsInstanceOf(valueObj, byteBufClass)) { + jmethodID positionID = + env->GetMethodID(byteBufClass, "position", "()I"); + CHECK(positionID != NULL); + + jmethodID limitID = + env->GetMethodID(byteBufClass, "limit", "()I"); + CHECK(limitID != NULL); + + jint position = env->CallIntMethod(valueObj, positionID); + jint limit = env->CallIntMethod(valueObj, limitID); + + sp<ABuffer> buffer = new ABuffer(limit - position); + + void *data = env->GetDirectBufferAddress(valueObj); + + if (data != NULL) { + memcpy(buffer->data(), + (const uint8_t *)data + position, + buffer->size()); + } else { + jmethodID arrayID = + env->GetMethodID(byteBufClass, "array", "()[B"); + CHECK(arrayID != NULL); + + jbyteArray byteArray = + (jbyteArray)env->CallObjectMethod(valueObj, arrayID); + CHECK(byteArray != NULL); + + env->GetByteArrayRegion( + byteArray, + position, + buffer->size(), + (jbyte *)buffer->data()); + + env->DeleteLocalRef(byteArray); byteArray = NULL; + } + + msg->setObject(key.c_str(), buffer); + } + } + + *out = msg; + + return OK; +} + } // namespace android diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h index a2c155a..635bceb 100644 --- a/media/jni/android_media_Utils.h +++ b/media/jni/android_media_Utils.h @@ -33,6 +33,14 @@ bool ConvertKeyValueArraysToKeyedVector( JNIEnv *env, jobjectArray keys, jobjectArray values, KeyedVector<String8, String8>* vector); +struct AMessage; +status_t ConvertMessageToMap( + JNIEnv *env, const sp<AMessage> &msg, jobject *map); + +status_t ConvertKeyValueArraysToMessage( + JNIEnv *env, jobjectArray keys, jobjectArray values, + sp<AMessage> *msg); + }; // namespace android #endif // _ANDROID_MEDIA_UTILS_H_ diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index ecd4d07..f015afb 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -23,6 +23,7 @@ #include <nativehelper/jni.h> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <utils/threads.h> #include "media/Visualizer.h" using namespace android; @@ -38,6 +39,7 @@ using namespace android; #define NATIVE_EVENT_PCM_CAPTURE 0 #define NATIVE_EVENT_FFT_CAPTURE 1 +#define NATIVE_EVENT_SERVER_DIED 2 // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/audiofx/Visualizer"; @@ -54,6 +56,43 @@ static fields_t fields; struct visualizer_callback_cookie { jclass visualizer_class; // Visualizer class jobject visualizer_ref; // Visualizer object instance + + // Lazily allocated arrays used to hold callback data provided to java + // applications. These arrays are allocated during the first callback and + // reallocated when the size of the callback data changes. Allocating on + // demand and saving the arrays means that applications cannot safely hold a + // reference to the provided data (they need to make a copy if they want to + // hold onto outside of the callback scope), but it avoids GC thrash caused + // by constantly allocating and releasing arrays to hold callback data. + Mutex callback_data_lock; + jbyteArray waveform_data; + jbyteArray fft_data; + + visualizer_callback_cookie() { + waveform_data = NULL; + fft_data = NULL; + } + + ~visualizer_callback_cookie() { + cleanupBuffers(); + } + + void cleanupBuffers() { + AutoMutex lock(&callback_data_lock); + if (waveform_data || fft_data) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + if (waveform_data) { + env->DeleteGlobalRef(waveform_data); + waveform_data = NULL; + } + + if (fft_data) { + env->DeleteGlobalRef(fft_data); + fft_data = NULL; + } + } + } }; // ---------------------------------------------------------------------------- @@ -66,7 +105,6 @@ class visualizerJniStorage { ~visualizerJniStorage() { } - }; @@ -93,6 +131,26 @@ static jint translateError(int code) { // ---------------------------------------------------------------------------- +static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { + if (NULL != *array) { + uint32_t len = env->GetArrayLength(*array); + if (len == size) + return; + + env->DeleteGlobalRef(*array); + *array = NULL; + } + + jbyteArray localRef = env->NewByteArray(size); + if (NULL != localRef) { + // Promote to global ref. + *array = (jbyteArray)env->NewGlobalRef(localRef); + + // Release our (now pointless) local ref. + env->DeleteLocalRef(localRef); + } +} + static void captureCallback(void* user, uint32_t waveformSize, uint8_t *waveform, @@ -106,6 +164,7 @@ static void captureCallback(void* user, visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user; JNIEnv *env = AndroidRuntime::getJNIEnv(); + AutoMutex lock(&callbackInfo->callback_data_lock); ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p", callbackInfo, @@ -118,7 +177,11 @@ static void captureCallback(void* user, } if (waveformSize != 0 && waveform != NULL) { - jbyteArray jArray = env->NewByteArray(waveformSize); + jbyteArray jArray; + + ensureArraySize(env, &callbackInfo->waveform_data, waveformSize); + jArray = callbackInfo->waveform_data; + if (jArray != NULL) { jbyte *nArray = env->GetByteArrayElements(jArray, NULL); memcpy(nArray, waveform, waveformSize); @@ -131,12 +194,15 @@ static void captureCallback(void* user, samplingrate, 0, jArray); - env->DeleteLocalRef(jArray); } } if (fftSize != 0 && fft != NULL) { - jbyteArray jArray = env->NewByteArray(fftSize); + jbyteArray jArray; + + ensureArraySize(env, &callbackInfo->fft_data, fftSize); + jArray = callbackInfo->fft_data; + if (jArray != NULL) { jbyte *nArray = env->GetByteArrayElements(jArray, NULL); memcpy(nArray, fft, fftSize); @@ -149,7 +215,6 @@ static void captureCallback(void* user, samplingrate, 0, jArray); - env->DeleteLocalRef(jArray); } } @@ -220,6 +285,23 @@ android_media_visualizer_native_init(JNIEnv *env) } +static void android_media_visualizer_effect_callback(int32_t event, + void *user, + void *info) { + if ((event == AudioEffect::EVENT_ERROR) && + (*((status_t*)info) == DEAD_OBJECT)) { + visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user; + visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData; + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->CallStaticVoidMethod( + callbackInfo->visualizer_class, + fields.midPostNativeEvent, + callbackInfo->visualizer_ref, + NATIVE_EVENT_SERVER_DIED, + 0, 0, 0); + } +} static jint android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, @@ -255,8 +337,8 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th // create the native Visualizer object lpVisualizer = new Visualizer(0, - NULL, - NULL, + android_media_visualizer_effect_callback, + lpJniStorage, sessionId); if (lpVisualizer == NULL) { ALOGE("Error creating Visualizer"); @@ -345,7 +427,17 @@ android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean e return VISUALIZER_ERROR_NO_INIT; } - return translateError(lpVisualizer->setEnabled(enabled)); + jint retVal = translateError(lpVisualizer->setEnabled(enabled)); + + if (!enabled) { + visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField( + thiz, fields.fidJniData); + + if (NULL != lpJniStorage) + lpJniStorage->mCallbackData.cleanupBuffers(); + } + + return retVal; } static jboolean diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp index c84a883..b0c1c35 100755 --- a/media/jni/mediaeditor/VideoEditorMain.cpp +++ b/media/jni/mediaeditor/VideoEditorMain.cpp @@ -29,8 +29,7 @@ #include <VideoEditorThumbnailMain.h> #include <M4OSA_Debug.h> #include <M4xVSS_Internal.h> -#include <surfaceflinger/Surface.h> -#include <surfaceflinger/ISurface.h> +#include <gui/Surface.h> #include "VideoEditorPreviewController.h" #include "VideoEditorMain.h" diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk new file mode 100644 index 0000000..54fd9ec --- /dev/null +++ b/media/libaah_rtp/Android.mk @@ -0,0 +1,40 @@ +LOCAL_PATH:= $(call my-dir) +# +# libaah_rtp +# + +include $(CLEAR_VARS) + +LOCAL_MODULE := libaah_rtp +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + aah_decoder_pump.cpp \ + aah_rx_player.cpp \ + aah_rx_player_core.cpp \ + aah_rx_player_ring_buffer.cpp \ + aah_rx_player_substream.cpp \ + aah_tx_packet.cpp \ + aah_tx_player.cpp \ + aah_tx_sender.cpp \ + pipe_event.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/include \ + frameworks/base/include/media/stagefright/openmax \ + frameworks/base/media \ + frameworks/base/media/libstagefright + +LOCAL_SHARED_LIBRARIES := \ + libcommon_time_client \ + libbinder \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libutils + +LOCAL_LDLIBS := \ + -lpthread + +include $(BUILD_SHARED_LIBRARY) + diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp new file mode 100644 index 0000000..28b8c7b --- /dev/null +++ b/media/libaah_rtp/aah_decoder_pump.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <poll.h> +#include <pthread.h> + +#include <common_time/cc_helper.h> +#include <media/AudioSystem.h> +#include <media/AudioTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXCodec.h> +#include <media/stagefright/Utils.h> +#include <utils/Timers.h> +#include <utils/threads.h> + +#include "aah_decoder_pump.h" + +namespace android { + +static const long long kLongDecodeErrorThreshold = 1000000ll; +static const uint32_t kMaxLongErrorsBeforeFatal = 3; +static const uint32_t kMaxErrorsBeforeFatal = 60; + +AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx) + : omx_(omx) + , thread_status_(OK) + , renderer_(NULL) + , last_queued_pts_valid_(false) + , last_queued_pts_(0) + , last_ts_transform_valid_(false) + , last_volume_(0xFF) { + thread_ = new ThreadWrapper(this); +} + +AAH_DecoderPump::~AAH_DecoderPump() { + shutdown(); +} + +status_t AAH_DecoderPump::initCheck() { + if (thread_ == NULL) { + ALOGE("Failed to allocate thread"); + return NO_MEMORY; + } + + return OK; +} + +status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) { + if (NULL == buf) { + return BAD_VALUE; + } + + if (OK != thread_status_) { + return thread_status_; + } + + { // Explicit scope for AutoMutex pattern. + AutoMutex lock(&thread_lock_); + in_queue_.push_back(buf); + } + + thread_cond_.signal(); + + return OK; +} + +void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) { + Mutex::Autolock lock(&render_lock_); + sp<MetaData> meta; + int64_t ts; + status_t res; + + // Fetch the metadata and make sure the sample has a timestamp. We + // cannot render samples which are missing PTSs. + meta = decoded_sample->meta_data(); + if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) { + ALOGV("Decoded sample missing timestamp, cannot render."); + CHECK(false); + } else { + // If we currently are not holding on to a renderer, go ahead and + // make one now. + if (NULL == renderer_) { + renderer_ = new TimedAudioTrack(); + if (NULL != renderer_) { + int frameCount; + AudioTrack::getMinFrameCount(&frameCount, + AUDIO_STREAM_DEFAULT, + static_cast<int>(format_sample_rate_)); + int ch_format = (format_channels_ == 1) + ? AUDIO_CHANNEL_OUT_MONO + : AUDIO_CHANNEL_OUT_STEREO; + + res = renderer_->set(AUDIO_STREAM_DEFAULT, + format_sample_rate_, + AUDIO_FORMAT_PCM_16_BIT, + ch_format, + frameCount); + if (res != OK) { + ALOGE("Failed to setup audio renderer. (res = %d)", res); + delete renderer_; + renderer_ = NULL; + } else { + CHECK(last_ts_transform_valid_); + + res = renderer_->setMediaTimeTransform( + last_ts_transform_, TimedAudioTrack::COMMON_TIME); + if (res != NO_ERROR) { + ALOGE("Failed to set media time transform on AudioTrack" + " (res = %d)", res); + delete renderer_; + renderer_ = NULL; + } else { + float volume = static_cast<float>(last_volume_) + / 255.0f; + if (renderer_->setVolume(volume, volume) != OK) { + ALOGW("%s: setVolume failed", __FUNCTION__); + } + + renderer_->start(); + } + } + } else { + ALOGE("Failed to allocate AudioTrack to use as a renderer."); + } + } + + if (NULL != renderer_) { + uint8_t* decoded_data = + reinterpret_cast<uint8_t*>(decoded_sample->data()); + uint32_t decoded_amt = decoded_sample->range_length(); + decoded_data += decoded_sample->range_offset(); + + sp<IMemory> pcm_payload; + res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload); + if (res != OK) { + ALOGE("Failed to allocate %d byte audio track buffer." + " (res = %d)", decoded_amt, res); + } else { + memcpy(pcm_payload->pointer(), decoded_data, decoded_amt); + + res = renderer_->queueTimedBuffer(pcm_payload, ts); + if (res != OK) { + ALOGE("Failed to queue %d byte audio track buffer with" + " media PTS %lld. (res = %d)", decoded_amt, ts, res); + } else { + last_queued_pts_valid_ = true; + last_queued_pts_ = ts; + } + } + + } else { + ALOGE("No renderer, dropping audio payload."); + } + } +} + +void AAH_DecoderPump::stopAndCleanupRenderer() { + if (NULL == renderer_) { + return; + } + + renderer_->stop(); + delete renderer_; + renderer_ = NULL; +} + +void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) { + Mutex::Autolock lock(&render_lock_); + + if (last_ts_transform_valid_ && !memcmp(&trans, + &last_ts_transform_, + sizeof(trans))) { + return; + } + + last_ts_transform_ = trans; + last_ts_transform_valid_ = true; + + if (NULL != renderer_) { + status_t res = renderer_->setMediaTimeTransform( + last_ts_transform_, TimedAudioTrack::COMMON_TIME); + if (res != NO_ERROR) { + ALOGE("Failed to set media time transform on AudioTrack" + " (res = %d)", res); + } + } +} + +void AAH_DecoderPump::setRenderVolume(uint8_t volume) { + Mutex::Autolock lock(&render_lock_); + + if (volume == last_volume_) { + return; + } + + last_volume_ = volume; + if (renderer_ != NULL) { + float volume = static_cast<float>(last_volume_) / 255.0f; + if (renderer_->setVolume(volume, volume) != OK) { + ALOGW("%s: setVolume failed", __FUNCTION__); + } + } +} + +// isAboutToUnderflow is something of a hack used to figure out when it might be +// time to give up on trying to fill in a gap in the RTP sequence and simply +// move on with a discontinuity. If we had perfect knowledge of when we were +// going to underflow, it would not be a hack, but unfortunately we do not. +// Right now, we just take the PTS of the last sample queued, and check to see +// if its presentation time is within kAboutToUnderflowThreshold from now. If +// it is, then we say that we are about to underflow. This decision is based on +// two (possibly invalid) assumptions. +// +// 1) The transmitter is leading the clock by more than +// kAboutToUnderflowThreshold. +// 2) The delta between the PTS of the last sample queued and the next sample +// is less than the transmitter's clock lead amount. +// +// Right now, the default transmitter lead time is 1 second, which is a pretty +// large number and greater than the 50mSec that kAboutToUnderflowThreshold is +// currently set to. This should satisfy assumption #1 for now, but changes to +// the transmitter clock lead time could effect this. +// +// For non-sparse streams with a homogeneous sample rate (the vast majority of +// streams in the world), the delta between any two adjacent PTSs will always be +// the homogeneous sample period. It is very uncommon to see a sample period +// greater than the 1 second clock lead we are currently using, and you +// certainly will not see it in an MP3 file which should satisfy assumption #2. +// Sparse audio streams (where no audio is transmitted for long periods of +// silence) and extremely low framerate video stream (like an MPEG-2 slideshow +// or the video stream for a pay TV audio channel) are examples of streams which +// might violate assumption #2. +bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) { + Mutex::Autolock lock(&render_lock_); + + // If we have never queued anything to the decoder, we really don't know if + // we are going to underflow or not. + if (!last_queued_pts_valid_ || !last_ts_transform_valid_) { + return false; + } + + // Don't have access to Common Time? If so, then things are Very Bad + // elsewhere in the system; it pretty much does not matter what we do here. + // Since we cannot really tell if we are about to underflow or not, its + // probably best to assume that we are not and proceed accordingly. + int64_t tt_now; + if (OK != cc_helper_.getCommonTime(&tt_now)) { + return false; + } + + // Transform from media time to common time. + int64_t last_queued_pts_tt; + if (!last_ts_transform_.doForwardTransform(last_queued_pts_, + &last_queued_pts_tt)) { + return false; + } + + // Check to see if we are underflowing. + return ((tt_now + threshold - last_queued_pts_tt) > 0); +} + +void* AAH_DecoderPump::workThread() { + // No need to lock when accessing decoder_ from the thread. The + // implementation of init and shutdown ensure that other threads never touch + // decoder_ while the work thread is running. + CHECK(decoder_ != NULL); + CHECK(format_ != NULL); + + // Start the decoder and note its result code. If something goes horribly + // wrong, callers of queueForDecode and getOutput will be able to detect + // that the thread encountered a fatal error and shut down by examining + // thread_status_. + thread_status_ = decoder_->start(format_.get()); + if (OK != thread_status_) { + ALOGE("AAH_DecoderPump's work thread failed to start decoder" + " (res = %d)", thread_status_); + return NULL; + } + + DurationTimer decode_timer; + uint32_t consecutive_long_errors = 0; + uint32_t consecutive_errors = 0; + + while (!thread_->exitPending()) { + status_t res; + MediaBuffer* bufOut = NULL; + + decode_timer.start(); + res = decoder_->read(&bufOut); + decode_timer.stop(); + + if (res == INFO_FORMAT_CHANGED) { + // Format has changed. Destroy our current renderer so that a new + // one can be created during queueToRenderer with the proper format. + // + // TODO : In order to transition seamlessly, we should change this + // to put the old renderer in a queue to play out completely before + // we destroy it. We can still create a new renderer, the timed + // nature of the renderer should ensure a seamless splice. + stopAndCleanupRenderer(); + res = OK; + } + + // Try to be a little nuanced in our handling of actual decode errors. + // Errors could happen because of minor stream corruption or because of + // transient resource limitations. In these cases, we would rather drop + // a little bit of output and ride out the unpleasantness then throw up + // our hands and abort everything. + // + // OTOH - When things are really bad (like we have a non-transient + // resource or bookkeeping issue, or the stream being fed to us is just + // complete and total garbage) we really want to terminate playback and + // raise an error condition all the way up to the application level so + // they can deal with it. + // + // Unfortunately, the error codes returned by the decoder can be a + // little non-specific. For example, if an OMXCodec times out + // attempting to obtain an output buffer, the error we get back is a + // generic -1. Try to distinguish between this resource timeout error + // and ES corruption error by timing how long the decode operation + // takes. Maintain accounting for both errors and "long errors". If we + // get more than a certain number consecutive errors of either type, + // consider it fatal and shutdown (which will cause the error to + // propagate all of the way up to the application level). The threshold + // for "long errors" is deliberately much lower than that of normal + // decode errors, both because of how long they take to happen and + // because they generally indicate resource limitation errors which are + // unlikely to go away in pathologically bad cases (in contrast to + // stream corruption errors which might happen 20 times in a row and + // then be suddenly OK again) + if (res != OK) { + consecutive_errors++; + if (decode_timer.durationUsecs() >= kLongDecodeErrorThreshold) + consecutive_long_errors++; + + CHECK(NULL == bufOut); + + ALOGW("%s: Failed to decode data (res = %d)", + __PRETTY_FUNCTION__, res); + + if ((consecutive_errors >= kMaxErrorsBeforeFatal) || + (consecutive_long_errors >= kMaxLongErrorsBeforeFatal)) { + ALOGE("%s: Maximum decode error threshold has been reached." + " There have been %d consecutive decode errors, and %d" + " consecutive decode operations which resulted in errors" + " and took more than %lld uSec to process. The last" + " decode operation took %lld uSec.", + __PRETTY_FUNCTION__, + consecutive_errors, consecutive_long_errors, + kLongDecodeErrorThreshold, decode_timer.durationUsecs()); + thread_status_ = res; + break; + } + + continue; + } + + if (NULL == bufOut) { + ALOGW("%s: Successful decode, but no buffer produced", + __PRETTY_FUNCTION__); + continue; + } + + // Successful decode (with actual output produced). Clear the error + // counters. + consecutive_errors = 0; + consecutive_long_errors = 0; + + queueToRenderer(bufOut); + bufOut->release(); + } + + decoder_->stop(); + stopAndCleanupRenderer(); + + return NULL; +} + +status_t AAH_DecoderPump::init(const sp<MetaData>& params) { + Mutex::Autolock lock(&init_lock_); + + if (decoder_ != NULL) { + // already inited + return OK; + } + + if (params == NULL) { + return BAD_VALUE; + } + + if (!params->findInt32(kKeyChannelCount, &format_channels_)) { + return BAD_VALUE; + } + + if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) { + return BAD_VALUE; + } + + CHECK(OK == thread_status_); + CHECK(decoder_ == NULL); + + status_t ret_val = UNKNOWN_ERROR; + + // Cache the format and attempt to create the decoder. + format_ = params; + decoder_ = OMXCodec::Create( + omx_.interface(), // IOMX Handle + format_, // Metadata for substream (indicates codec) + false, // Make a decoder, not an encoder + sp<MediaSource>(this)); // We will be the source for this codec. + + if (decoder_ == NULL) { + ALOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__); + goto bailout; + } + + // Fire up the pump thread. It will take care of starting and stopping the + // decoder. + ret_val = thread_->run("aah_decode_pump", ANDROID_PRIORITY_AUDIO); + if (OK != ret_val) { + ALOGE("Failed to start work thread in %s (res = %d)", + __PRETTY_FUNCTION__, ret_val); + goto bailout; + } + +bailout: + if (OK != ret_val) { + decoder_ = NULL; + format_ = NULL; + } + + return OK; +} + +status_t AAH_DecoderPump::shutdown() { + Mutex::Autolock lock(&init_lock_); + return shutdown_l(); +} + +status_t AAH_DecoderPump::shutdown_l() { + thread_->requestExit(); + thread_cond_.signal(); + thread_->requestExitAndWait(); + + for (MBQueue::iterator iter = in_queue_.begin(); + iter != in_queue_.end(); + ++iter) { + (*iter)->release(); + } + in_queue_.clear(); + + last_queued_pts_valid_ = false; + last_ts_transform_valid_ = false; + last_volume_ = 0xFF; + thread_status_ = OK; + + decoder_ = NULL; + format_ = NULL; + + return OK; +} + +status_t AAH_DecoderPump::read(MediaBuffer **buffer, + const ReadOptions *options) { + if (!buffer) { + return BAD_VALUE; + } + + *buffer = NULL; + + // While its not time to shut down, and we have no data to process, wait. + AutoMutex lock(&thread_lock_); + while (!thread_->exitPending() && in_queue_.empty()) + thread_cond_.wait(thread_lock_); + + // At this point, if its not time to shutdown then we must have something to + // process. Go ahead and pop the front of the queue for processing. + if (!thread_->exitPending()) { + CHECK(!in_queue_.empty()); + + *buffer = *(in_queue_.begin()); + in_queue_.erase(in_queue_.begin()); + } + + // If we managed to get a buffer, then everything must be OK. If not, then + // we must be shutting down. + return (NULL == *buffer) ? INVALID_OPERATION : OK; +} + +AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner) + : Thread(false /* canCallJava*/ ) + , owner_(owner) { +} + +bool AAH_DecoderPump::ThreadWrapper::threadLoop() { + CHECK(NULL != owner_); + owner_->workThread(); + return false; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h new file mode 100644 index 0000000..f5a6529 --- /dev/null +++ b/media/libaah_rtp/aah_decoder_pump.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __DECODER_PUMP_H__ +#define __DECODER_PUMP_H__ + +#include <pthread.h> + +#include <common_time/cc_helper.h> +#include <media/stagefright/MediaSource.h> +#include <utils/LinearTransform.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class MetaData; +class OMXClient; +class TimedAudioTrack; + +class AAH_DecoderPump : public MediaSource { + public: + explicit AAH_DecoderPump(OMXClient& omx); + status_t initCheck(); + + status_t queueForDecode(MediaBuffer* buf); + + status_t init(const sp<MetaData>& params); + status_t shutdown(); + + void setRenderTSTransform(const LinearTransform& trans); + void setRenderVolume(uint8_t volume); + bool isAboutToUnderflow(int64_t threshold); + bool getStatus() const { return thread_status_; } + + // MediaSource methods + virtual status_t start(MetaData *params) { return OK; } + virtual sp<MetaData> getFormat() { return format_; } + virtual status_t stop() { return OK; } + virtual status_t read(MediaBuffer **buffer, + const ReadOptions *options); + + protected: + virtual ~AAH_DecoderPump(); + + private: + class ThreadWrapper : public Thread { + public: + friend class AAH_DecoderPump; + explicit ThreadWrapper(AAH_DecoderPump* owner); + + private: + virtual bool threadLoop(); + AAH_DecoderPump* owner_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper); + }; + + void* workThread(); + virtual status_t shutdown_l(); + void queueToRenderer(MediaBuffer* decoded_sample); + void stopAndCleanupRenderer(); + + sp<MetaData> format_; + int32_t format_channels_; + int32_t format_sample_rate_; + + sp<MediaSource> decoder_; + OMXClient& omx_; + Mutex init_lock_; + + sp<ThreadWrapper> thread_; + Condition thread_cond_; + Mutex thread_lock_; + status_t thread_status_; + + Mutex render_lock_; + TimedAudioTrack* renderer_; + bool last_queued_pts_valid_; + int64_t last_queued_pts_; + bool last_ts_transform_valid_; + LinearTransform last_ts_transform_; + uint8_t last_volume_; + CCHelper cc_helper_; + + // protected by the thread_lock_ + typedef List<MediaBuffer*> MBQueue; + MBQueue in_queue_; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump); +}; + +} // namespace android +#endif // __DECODER_PUMP_H__ diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp new file mode 100644 index 0000000..9dd79fd --- /dev/null +++ b/media/libaah_rtp/aah_rx_player.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 + +#include <binder/IServiceManager.h> +#include <media/MediaPlayerInterface.h> +#include <utils/Log.h> + +#include "aah_rx_player.h" + +namespace android { + +const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10; + +sp<MediaPlayerBase> createAAH_RXPlayer() { + sp<MediaPlayerBase> ret = new AAH_RXPlayer(); + return ret; +} + +AAH_RXPlayer::AAH_RXPlayer() + : ring_buffer_(kRTPRingBufferSize) + , substreams_(NULL) { + thread_wrapper_ = new ThreadWrapper(*this); + + is_playing_ = false; + multicast_joined_ = false; + transmitter_known_ = false; + current_epoch_known_ = false; + data_source_set_ = false; + sock_fd_ = -1; + + substreams_.setCapacity(4); + + memset(&listen_addr_, 0, sizeof(listen_addr_)); + memset(&transmitter_addr_, 0, sizeof(transmitter_addr_)); + + fetchAudioFlinger(); +} + +AAH_RXPlayer::~AAH_RXPlayer() { + reset_l(); + CHECK(substreams_.size() == 0); + omx_.disconnect(); +} + +status_t AAH_RXPlayer::initCheck() { + if (thread_wrapper_ == NULL) { + ALOGE("Failed to allocate thread wrapper!"); + return NO_MEMORY; + } + + if (!ring_buffer_.initCheck()) { + ALOGE("Failed to allocate reassembly ring buffer!"); + return NO_MEMORY; + } + + // Check for the presense of the common time service by attempting to query + // for CommonTime's frequency. If we get an error back, we cannot talk to + // the service at all and should abort now. + status_t res; + uint64_t freq; + res = cc_helper_.getCommonFreq(&freq); + if (OK != res) { + ALOGE("Failed to connect to common time service!"); + return res; + } + + return omx_.connect(); +} + +status_t AAH_RXPlayer::setDataSource( + const char *url, + const KeyedVector<String8, String8> *headers) { + AutoMutex api_lock(&api_lock_); + uint32_t a, b, c, d; + uint16_t port; + + if (data_source_set_) { + return INVALID_OPERATION; + } + + if (NULL == url) { + return BAD_VALUE; + } + + if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) { + ALOGE("Failed to parse URL \"%s\"", url); + return BAD_VALUE; + } + + if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) { + ALOGE("Bad multicast address \"%s\"", url); + return BAD_VALUE; + } + + ALOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port); + + a = (a << 24) | (b << 16) | (c << 8) | d; + + memset(&listen_addr_, 0, sizeof(listen_addr_)); + listen_addr_.sin_family = AF_INET; + listen_addr_.sin_port = htons(port); + listen_addr_.sin_addr.s_addr = htonl(a); + data_source_set_ = true; + + return OK; +} + +status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + return INVALID_OPERATION; +} + +status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) { + return OK; +} + +status_t AAH_RXPlayer::setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) { + return OK; +} + +status_t AAH_RXPlayer::prepare() { + return OK; +} + +status_t AAH_RXPlayer::prepareAsync() { + sendEvent(MEDIA_PREPARED); + return OK; +} + +status_t AAH_RXPlayer::start() { + AutoMutex api_lock(&api_lock_); + + if (is_playing_) { + return OK; + } + + status_t res = startWorkThread(); + is_playing_ = (res == OK); + return res; +} + +status_t AAH_RXPlayer::stop() { + return pause(); +} + +status_t AAH_RXPlayer::pause() { + AutoMutex api_lock(&api_lock_); + stopWorkThread(); + CHECK(sock_fd_ < 0); + is_playing_ = false; + return OK; +} + +bool AAH_RXPlayer::isPlaying() { + AutoMutex api_lock(&api_lock_); + return is_playing_; +} + +status_t AAH_RXPlayer::seekTo(int msec) { + sendEvent(MEDIA_SEEK_COMPLETE); + return OK; +} + +status_t AAH_RXPlayer::getCurrentPosition(int *msec) { + if (NULL != msec) { + *msec = 0; + } + return OK; +} + +status_t AAH_RXPlayer::getDuration(int *msec) { + if (NULL != msec) { + *msec = 1; + } + return OK; +} + +status_t AAH_RXPlayer::reset() { + AutoMutex api_lock(&api_lock_); + reset_l(); + return OK; +} + +void AAH_RXPlayer::reset_l() { + stopWorkThread(); + CHECK(sock_fd_ < 0); + CHECK(!multicast_joined_); + is_playing_ = false; + data_source_set_ = false; + transmitter_known_ = false; + memset(&listen_addr_, 0, sizeof(listen_addr_)); +} + +status_t AAH_RXPlayer::setLooping(int loop) { + return OK; +} + +player_type AAH_RXPlayer::playerType() { + return AAH_RX_PLAYER; +} + +status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) { + if (!reply) { + return BAD_VALUE; + } + + int32_t magic; + status_t err = request.readInt32(&magic); + if (err != OK) { + reply->writeInt32(err); + return OK; + } + + if (magic != 0x12345) { + reply->writeInt32(BAD_VALUE); + return OK; + } + + int32_t methodID; + err = request.readInt32(&methodID); + if (err != OK) { + reply->writeInt32(err); + return OK; + } + + switch (methodID) { + // Get Volume + case INVOKE_GET_MASTER_VOLUME: { + if (audio_flinger_ != NULL) { + reply->writeInt32(OK); + reply->writeFloat(audio_flinger_->masterVolume()); + } else { + reply->writeInt32(UNKNOWN_ERROR); + } + } break; + + // Set Volume + case INVOKE_SET_MASTER_VOLUME: { + float targetVol = request.readFloat(); + reply->writeInt32(audio_flinger_->setMasterVolume(targetVol)); + } break; + + default: return BAD_VALUE; + } + + return OK; +} + +void AAH_RXPlayer::fetchAudioFlinger() { + if (audio_flinger_ == NULL) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder; + binder = sm->getService(String16("media.audio_flinger")); + + if (binder == NULL) { + ALOGW("AAH_RXPlayer failed to fetch handle to audio flinger." + " Master volume control will not be possible."); + } + + audio_flinger_ = interface_cast<IAudioFlinger>(binder); + } +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h new file mode 100644 index 0000000..ba5617e --- /dev/null +++ b/media/libaah_rtp/aah_rx_player.h @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __AAH_RX_PLAYER_H__ +#define __AAH_RX_PLAYER_H__ + +#include <common_time/cc_helper.h> +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXClient.h> +#include <netinet/in.h> +#include <utils/KeyedVector.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +#include "aah_decoder_pump.h" +#include "pipe_event.h" + +namespace android { + +class AAH_RXPlayer : public MediaPlayerInterface { + public: + AAH_RXPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url, + const KeyedVector<String8, String8>* + headers); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<Surface>& surface); + virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>& + surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + virtual status_t invoke(const Parcel& request, Parcel *reply); + + protected: + virtual ~AAH_RXPlayer(); + + private: + class ThreadWrapper : public Thread { + public: + friend class AAH_RXPlayer; + explicit ThreadWrapper(AAH_RXPlayer& player) + : Thread(false /* canCallJava */ ) + , player_(player) { } + + virtual bool threadLoop() { return player_.threadLoop(); } + + private: + AAH_RXPlayer& player_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper); + }; + +#pragma pack(push, 1) + // PacketBuffers are structures used by the RX ring buffer. The ring buffer + // is a ring of pointers to PacketBuffer structures which act as variable + // length byte arrays and hold the contents of received UDP packets. Rather + // than make this a structure which hold a length and a pointer to another + // allocated structure (which would require two allocations), this struct + // uses a structure overlay pattern where allocation for the byte array + // consists of allocating (arrayLen + sizeof(ssize_t)) bytes of data from + // whatever pool/heap the packet buffer pulls from, and then overlaying the + // packed PacketBuffer structure on top of the allocation. The one-byte + // array at the end of the structure serves as an offset to the the data + // portion of the allocation; packet buffers are never allocated on the + // stack or using the new operator. Instead, the static allocate-byte-array + // and destroy methods handle the allocate and overlay pattern. They also + // allow for a potential future optimization where instead of just + // allocating blocks from the process global heap and overlaying, the + // allocator is replaced with a different implementation (private heap, + // free-list, circular buffer, etc) which reduces potential heap + // fragmentation issues which might arise from the frequent allocation and + // destruction of the received UDP traffic. + struct PacketBuffer { + ssize_t length_; + uint8_t data_[1]; + + // TODO : consider changing this to be some form of ring buffer or free + // pool system instead of just using the heap in order to avoid heap + // fragmentation. + static PacketBuffer* allocate(ssize_t length); + static void destroy(PacketBuffer* pb); + + private: + // Force people to use allocate/destroy instead of new/delete. + PacketBuffer() { } + ~PacketBuffer() { } + }; + + struct RetransRequest { + uint32_t magic_; + uint32_t mcast_ip_; + uint16_t mcast_port_; + uint16_t start_seq_; + uint16_t end_seq_; + }; +#pragma pack(pop) + + enum GapStatus { + kGS_NoGap = 0, + kGS_NormalGap, + kGS_FastStartGap, + }; + + struct SeqNoGap { + uint16_t start_seq_; + uint16_t end_seq_; + }; + + class RXRingBuffer { + public: + explicit RXRingBuffer(uint32_t capacity); + ~RXRingBuffer(); + + bool initCheck() const { return (ring_ != NULL); } + void reset(); + + // Push a packet buffer with a given sequence number into the ring + // buffer. pushBuffer will always consume the buffer pushed to it, + // either destroying it because it was a duplicate or overflow, or + // holding on to it in the ring. Callers should not hold any references + // to PacketBuffers after they have been pushed to the ring. Returns + // false in the case of a serious error (such as ring overflow). + // Callers should consider resetting the pipeline entirely in the event + // of a serious error. + bool pushBuffer(PacketBuffer* buf, uint16_t seq); + + // Fetch the next buffer in the RTP sequence. Returns NULL if there is + // no buffer to fetch. If a non-NULL PacketBuffer is returned, + // is_discon will be set to indicate whether or not this PacketBuffer is + // discontiuous with any previously returned packet buffers. Packet + // buffers returned by fetchBuffer are the caller's responsibility; they + // must be certain to destroy the buffers when they are done. + PacketBuffer* fetchBuffer(bool* is_discon); + + // Returns true and fills out the gap structure if the read pointer of + // the ring buffer is currently pointing to a gap which would stall a + // fetchBuffer operation. Returns false if the read pointer is not + // pointing to a gap in the sequence currently. + GapStatus fetchCurrentGap(SeqNoGap* gap); + + // Causes the read pointer to skip over any portion of a gap indicated + // by nak. If nak is NULL, any gap currently blocking the read pointer + // will be completely skipped. If any portion of a gap is skipped, the + // next successful read from fetch buffer will indicate a discontinuity. + void processNAK(const SeqNoGap* nak = NULL); + + // Compute the number of milliseconds until the inactivity timer for + // this RTP stream. Returns -1 if there is no active timeout, or 0 if + // the system has already timed out. + int computeInactivityTimeout(); + + private: + Mutex lock_; + PacketBuffer** ring_; + uint32_t capacity_; + uint32_t rd_; + uint32_t wr_; + + uint16_t rd_seq_; + bool rd_seq_known_; + bool waiting_for_fast_start_; + bool fetched_first_packet_; + + uint64_t rtp_activity_timeout_; + bool rtp_activity_timeout_valid_; + + DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer); + }; + + class Substream : public virtual RefBase { + public: + Substream(uint32_t ssrc, OMXClient& omx); + + void cleanupBufferInProgress(); + void shutdown(); + void processPayloadStart(uint8_t* buf, + uint32_t amt, + int32_t ts_lower); + void processPayloadCont (uint8_t* buf, + uint32_t amt); + void processTSTransform(const LinearTransform& trans); + + bool isAboutToUnderflow(); + uint32_t getSSRC() const { return ssrc_; } + uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; } + status_t getStatus() const { return status_; } + + protected: + virtual ~Substream(); + + private: + void cleanupDecoder(); + bool shouldAbort(const char* log_tag); + void processCompletedBuffer(); + bool setupSubstreamMeta(); + bool setupMP3SubstreamMeta(); + bool setupAACSubstreamMeta(); + bool setupSubstreamType(uint8_t substream_type, + uint8_t codec_type); + + uint32_t ssrc_; + bool waiting_for_rap_; + status_t status_; + + bool substream_details_known_; + uint8_t substream_type_; + uint8_t codec_type_; + const char* codec_mime_type_; + sp<MetaData> substream_meta_; + + MediaBuffer* buffer_in_progress_; + uint32_t expected_buffer_size_; + uint32_t buffer_filled_; + + Vector<uint8_t> aux_data_in_progress_; + uint32_t aux_data_expected_size_; + + sp<AAH_DecoderPump> decoder_; + + static int64_t kAboutToUnderflowThreshold; + + DISALLOW_EVIL_CONSTRUCTORS(Substream); + }; + + typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec; + + status_t startWorkThread(); + void stopWorkThread(); + virtual bool threadLoop(); + bool setupSocket(); + void cleanupSocket(); + void resetPipeline(); + void reset_l(); + bool processRX(PacketBuffer* pb); + void processRingBuffer(); + void processCommandPacket(PacketBuffer* pb); + bool processGaps(); + int computeNextGapRetransmitTimeout(); + void fetchAudioFlinger(); + + PipeEvent wakeup_work_thread_evt_; + sp<ThreadWrapper> thread_wrapper_; + Mutex api_lock_; + bool is_playing_; + bool data_source_set_; + + struct sockaddr_in listen_addr_; + int sock_fd_; + bool multicast_joined_; + + struct sockaddr_in transmitter_addr_; + bool transmitter_known_; + + uint32_t current_epoch_; + bool current_epoch_known_; + + SeqNoGap current_gap_; + GapStatus current_gap_status_; + uint64_t next_retrans_req_time_; + + RXRingBuffer ring_buffer_; + SubstreamVec substreams_; + OMXClient omx_; + CCHelper cc_helper_; + + // Connection to audio flinger used to hack a path to setMasterVolume. + sp<IAudioFlinger> audio_flinger_; + + static const uint32_t kRTPRingBufferSize; + static const uint32_t kRetransRequestMagic; + static const uint32_t kFastStartRequestMagic; + static const uint32_t kRetransNAKMagic; + static const uint32_t kGapRerequestTimeoutUSec; + static const uint32_t kFastStartTimeoutUSec; + static const uint32_t kRTPActivityTimeoutUSec; + + static const uint32_t INVOKE_GET_MASTER_VOLUME = 3; + static const uint32_t INVOKE_SET_MASTER_VOLUME = 4; + + static uint64_t monotonicUSecNow(); + + DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer); +}; + +} // namespace android + +#endif // __AAH_RX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp new file mode 100644 index 0000000..d6b31fd --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_core.cpp @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <fcntl.h> +#include <poll.h> +#include <sys/socket.h> +#include <time.h> +#include <utils/misc.h> + +#include <media/stagefright/Utils.h> + +#include "aah_rx_player.h" +#include "aah_tx_packet.h" + +namespace android { + +const uint32_t AAH_RXPlayer::kRetransRequestMagic = + FOURCC('T','r','e','q'); +const uint32_t AAH_RXPlayer::kRetransNAKMagic = + FOURCC('T','n','a','k'); +const uint32_t AAH_RXPlayer::kFastStartRequestMagic = + FOURCC('T','f','s','t'); +const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000; +const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000; +const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000; + +static inline int16_t fetchInt16(uint8_t* data) { + return static_cast<int16_t>(U16_AT(data)); +} + +static inline int32_t fetchInt32(uint8_t* data) { + return static_cast<int32_t>(U32_AT(data)); +} + +static inline int64_t fetchInt64(uint8_t* data) { + return static_cast<int64_t>(U64_AT(data)); +} + +uint64_t AAH_RXPlayer::monotonicUSecNow() { + struct timespec now; + int res = clock_gettime(CLOCK_MONOTONIC, &now); + CHECK(res >= 0); + + uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000; + ret += now.tv_nsec / 1000; + + return ret; +} + +status_t AAH_RXPlayer::startWorkThread() { + status_t res; + stopWorkThread(); + res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO); + + if (res != OK) { + ALOGE("Failed to start work thread (res = %d)", res); + } + + return res; +} + +void AAH_RXPlayer::stopWorkThread() { + thread_wrapper_->requestExit(); // set the exit pending flag + wakeup_work_thread_evt_.setEvent(); + + status_t res; + res = thread_wrapper_->requestExitAndWait(); // block until thread exit. + if (res != OK) { + ALOGE("Failed to stop work thread (res = %d)", res); + } + + wakeup_work_thread_evt_.clearPendingEvents(); +} + +void AAH_RXPlayer::cleanupSocket() { + if (sock_fd_ >= 0) { + if (multicast_joined_) { + int res; + struct ip_mreq mreq; + mreq.imr_multiaddr = listen_addr_.sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + res = setsockopt(sock_fd_, + IPPROTO_IP, + IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (res < 0) { + ALOGW("Failed to leave multicast group. (%d, %d)", res, errno); + } + multicast_joined_ = false; + } + + close(sock_fd_); + sock_fd_ = -1; + } + + resetPipeline(); +} + +void AAH_RXPlayer::resetPipeline() { + ring_buffer_.reset(); + + // Explicitly shudown all of the active substreams, then call clear out the + // collection. Failure to clear out a substream can result in its decoder + // holding a reference to itself and therefor not going away when the + // collection is cleared. + for (size_t i = 0; i < substreams_.size(); ++i) + substreams_.valueAt(i)->shutdown(); + + substreams_.clear(); + + current_gap_status_ = kGS_NoGap; +} + +bool AAH_RXPlayer::setupSocket() { + long flags; + int res, buf_size; + socklen_t opt_size; + + cleanupSocket(); + CHECK(sock_fd_ < 0); + + // Make the socket + sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock_fd_ < 0) { + ALOGE("Failed to create listen socket (errno %d)", errno); + goto bailout; + } + + // Set non-blocking operation + flags = fcntl(sock_fd_, F_GETFL); + res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK); + if (res < 0) { + ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", + sock_fd_, errno); + goto bailout; + } + + // Bind to our port + struct sockaddr_in bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = INADDR_ANY; + bind_addr.sin_port = listen_addr_.sin_port; + res = bind(sock_fd_, + reinterpret_cast<const sockaddr*>(&bind_addr), + sizeof(bind_addr)); + if (res < 0) { + uint32_t a = ntohl(bind_addr.sin_addr.s_addr); + uint16_t p = ntohs(bind_addr.sin_port); + ALOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)", + sock_fd_, + (a >> 24) & 0xFF, + (a >> 16) & 0xFF, + (a >> 8) & 0xFF, + (a ) & 0xFF, + p, + errno); + + goto bailout; + } + + buf_size = 1 << 16; // 64k + res = setsockopt(sock_fd_, + SOL_SOCKET, SO_RCVBUF, + &buf_size, sizeof(buf_size)); + if (res < 0) { + ALOGW("Failed to increase socket buffer size to %d. (errno %d)", + buf_size, errno); + } + + buf_size = 0; + opt_size = sizeof(buf_size); + res = getsockopt(sock_fd_, + SOL_SOCKET, SO_RCVBUF, + &buf_size, &opt_size); + if (res < 0) { + ALOGW("Failed to fetch socket buffer size. (errno %d)", errno); + } else { + ALOGI("RX socket buffer size is now %d bytes", buf_size); + } + + if (listen_addr_.sin_addr.s_addr) { + // Join the multicast group and we should be good to go. + struct ip_mreq mreq; + mreq.imr_multiaddr = listen_addr_.sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + res = setsockopt(sock_fd_, + IPPROTO_IP, + IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (res < 0) { + ALOGE("Failed to join multicast group. (errno %d)", errno); + goto bailout; + } + multicast_joined_ = true; + } + + return true; + +bailout: + cleanupSocket(); + return false; +} + +bool AAH_RXPlayer::threadLoop() { + struct pollfd poll_fds[2]; + bool process_more_right_now = false; + + if (!setupSocket()) { + sendEvent(MEDIA_ERROR); + goto bailout; + } + + while (!thread_wrapper_->exitPending()) { + // Step 1: Wait until there is something to do. + int gap_timeout = computeNextGapRetransmitTimeout(); + int ring_timeout = ring_buffer_.computeInactivityTimeout(); + int timeout = -1; + + if (!ring_timeout) { + ALOGW("RTP inactivity timeout reached, resetting pipeline."); + resetPipeline(); + timeout = gap_timeout; + } else { + if (gap_timeout < 0) { + timeout = ring_timeout; + } else if (ring_timeout < 0) { + timeout = gap_timeout; + } else { + timeout = (gap_timeout < ring_timeout) ? gap_timeout + : ring_timeout; + } + } + + if ((0 != timeout) && (!process_more_right_now)) { + // Set up the events to wait on. Start with the wakeup pipe. + memset(&poll_fds, 0, sizeof(poll_fds)); + poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle(); + poll_fds[0].events = POLLIN; + + // Add the RX socket. + poll_fds[1].fd = sock_fd_; + poll_fds[1].events = POLLIN; + + // Wait for something interesing to happen. + int poll_res = poll(poll_fds, NELEM(poll_fds), timeout); + if (poll_res < 0) { + ALOGE("Fatal error (%d,%d) while waiting on events", + poll_res, errno); + sendEvent(MEDIA_ERROR); + goto bailout; + } + } + + if (thread_wrapper_->exitPending()) { + break; + } + + wakeup_work_thread_evt_.clearPendingEvents(); + process_more_right_now = false; + + // Step 2: Do we have data waiting in the socket? If so, drain the + // socket moving valid RTP information into the ring buffer to be + // processed. + if (poll_fds[1].revents) { + struct sockaddr_in from; + socklen_t from_len; + + ssize_t res = 0; + while (!thread_wrapper_->exitPending()) { + // Check the size of any pending packet. + res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC); + + // Error? + if (res < 0) { + // If the error is anything other than would block, + // something has gone very wrong. + if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) { + ALOGE("Fatal socket error during recvfrom (%d, %d)", + (int)res, errno); + goto bailout; + } + + // Socket is out of data, just break out of processing and + // wait for more. + break; + } + + // Allocate a payload. + PacketBuffer* pb = PacketBuffer::allocate(res); + if (NULL == pb) { + ALOGE("Fatal error, failed to allocate packet buffer of" + " length %u", static_cast<uint32_t>(res)); + goto bailout; + } + + // Fetch the data. + from_len = sizeof(from); + res = recvfrom(sock_fd_, pb->data_, pb->length_, 0, + reinterpret_cast<struct sockaddr*>(&from), + &from_len); + if (res != pb->length_) { + ALOGE("Fatal error, fetched packet length (%d) does not" + " match peeked packet length (%u). This should never" + " happen. (errno = %d)", + static_cast<int>(res), + static_cast<uint32_t>(pb->length_), + errno); + } + + bool drop_packet = false; + if (transmitter_known_) { + if (from.sin_addr.s_addr != + transmitter_addr_.sin_addr.s_addr) { + uint32_t a = ntohl(from.sin_addr.s_addr); + uint16_t p = ntohs(from.sin_port); + ALOGV("Dropping packet from unknown transmitter" + " %u.%u.%u.%u:%hu", + ((a >> 24) & 0xFF), + ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), + ( a & 0xFF), + p); + + drop_packet = true; + } else { + transmitter_addr_.sin_port = from.sin_port; + } + } else { + memcpy(&transmitter_addr_, &from, sizeof(from)); + transmitter_known_ = true; + } + + if (!drop_packet) { + bool serious_error = !processRX(pb); + + if (serious_error) { + // Something went "seriously wrong". Currently, the + // only trigger for this should be a ring buffer + // overflow. The current failsafe behavior for when + // something goes seriously wrong is to just reset the + // pipeline. The system should behave as if this + // AAH_RXPlayer was just set up for the first time. + ALOGE("Something just went seriously wrong with the" + " pipeline. Resetting."); + resetPipeline(); + } + } else { + PacketBuffer::destroy(pb); + } + } + } + + // Step 3: Process any data we mave have accumulated in the ring buffer + // so far. + if (!thread_wrapper_->exitPending()) { + processRingBuffer(); + } + + // Step 4: At this point in time, the ring buffer should either be + // empty, or stalled in front of a gap caused by some dropped packets. + // Check on the current gap situation and deal with it in an appropriate + // fashion. If processGaps returns true, it means that it has given up + // on a gap and that we should try to process some more data + // immediately. + if (!thread_wrapper_->exitPending()) { + process_more_right_now = processGaps(); + } + + // Step 5: Check for fatal errors. If any of our substreams has + // encountered a fatal, unrecoverable, error, then propagate the error + // up to user level and shut down. + for (size_t i = 0; i < substreams_.size(); ++i) { + status_t status; + CHECK(substreams_.valueAt(i) != NULL); + + status = substreams_.valueAt(i)->getStatus(); + if (OK != status) { + ALOGE("Substream index %d has encountered an unrecoverable" + " error (%d). Signalling application level and shutting" + " down.", i, status); + sendEvent(MEDIA_ERROR); + goto bailout; + } + } + } + +bailout: + cleanupSocket(); + return false; +} + +bool AAH_RXPlayer::processRX(PacketBuffer* pb) { + CHECK(NULL != pb); + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + uint32_t nak_magic; + uint16_t seq_no; + uint32_t epoch; + + // Every packet either starts with an RTP header which is at least 12 bytes + // long or is a retry NAK which is 14 bytes long. If there are fewer than + // 12 bytes here, this cannot be a proper RTP packet. + if (amt < 12) { + ALOGV("Dropping packet, too short to contain RTP header (%u bytes)", + static_cast<uint32_t>(amt)); + goto drop_packet; + } + + // Check to see if this is the special case of a NAK packet. + nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data))); + if (nak_magic == kRetransNAKMagic) { + // Looks like a NAK packet; make sure its long enough. + + if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) { + ALOGV("Dropping packet, too short to contain NAK payload" + " (%u bytes)", static_cast<uint32_t>(amt)); + goto drop_packet; + } + + SeqNoGap gap; + RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data); + gap.start_seq_ = ntohs(rtr->start_seq_); + gap.end_seq_ = ntohs(rtr->end_seq_); + + ALOGV("Process NAK for gap at [%hu, %hu]", + gap.start_seq_, gap.end_seq_); + ring_buffer_.processNAK(&gap); + + return true; + } + + // According to the TRTP spec, version should be 2, padding should be 0, + // extension should be 0 and CSRCCnt should be 0. If any of these tests + // fail, we chuck the packet. + if (data[0] != 0x80) { + ALOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)", + data[0]); + goto drop_packet; + } + + // Check the payload type. For TRTP, it should always be 100. + if ((data[1] & 0x7F) != 100) { + ALOGV("Dropping packet, bad payload type. (%u)", + data[1] & 0x7F); + goto drop_packet; + } + + // Check whether the transmitter has begun a new epoch. + epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF; + if (current_epoch_known_) { + if (epoch != current_epoch_) { + ALOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch); + current_epoch_ = epoch; + resetPipeline(); + } + } else { + current_epoch_ = epoch; + current_epoch_known_ = true; + } + + // Extract the sequence number and hand the packet off to the ring buffer + // for dropped packet detection and later processing. + seq_no = U16_AT(data + 2); + return ring_buffer_.pushBuffer(pb, seq_no); + +drop_packet: + PacketBuffer::destroy(pb); + return true; +} + +void AAH_RXPlayer::processRingBuffer() { + PacketBuffer* pb; + bool is_discon; + sp<Substream> substream; + LinearTransform trans; + bool foundTrans = false; + + while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) { + if (is_discon) { + // Abort all partially assembled payloads. + for (size_t i = 0; i < substreams_.size(); ++i) { + CHECK(substreams_.valueAt(i) != NULL); + substreams_.valueAt(i)->cleanupBufferInProgress(); + } + } + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + + // Should not have any non-RTP packets in the ring buffer. RTP packets + // must be at least 12 bytes long. + CHECK(amt >= 12); + + // Extract the marker bit and the SSRC field. + bool marker = (data[1] & 0x80) != 0; + uint32_t ssrc = U32_AT(data + 8); + + // Is this the start of a new TRTP payload? If so, the marker bit + // should be set and there are some things we should be checking for. + if (marker) { + // TRTP headers need to have at least a byte for version, a byte for + // payload type and flags, and 4 bytes for length. + if (amt < 18) { + ALOGV("Dropping packet, too short to contain TRTP header" + " (%u bytes)", static_cast<uint32_t>(amt)); + goto process_next_packet; + } + + // Check the TRTP version and extract the payload type/flags. + uint8_t trtp_version = data[12]; + uint8_t payload_type = (data[13] >> 4) & 0xF; + uint8_t trtp_flags = data[13] & 0xF; + + if (1 != trtp_version) { + ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); + goto process_next_packet; + } + + // Is there a timestamp transformation present on this packet? If + // so, extract it and pass it to the appropriate substreams. + if (trtp_flags & 0x02) { + ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0); + if (amt < (offset + 24)) { + ALOGV("Dropping packet, too short to contain TRTP Timestamp" + " Transformation (%u bytes)", + static_cast<uint32_t>(amt)); + goto process_next_packet; + } + + trans.a_zero = fetchInt64(data + offset); + trans.b_zero = fetchInt64(data + offset + 16); + trans.a_to_b_numer = static_cast<int32_t>( + fetchInt32 (data + offset + 8)); + trans.a_to_b_denom = U32_AT(data + offset + 12); + foundTrans = true; + + uint32_t program_id = (ssrc >> 5) & 0x1F; + for (size_t i = 0; i < substreams_.size(); ++i) { + sp<Substream> iter = substreams_.valueAt(i); + CHECK(iter != NULL); + + if (iter->getProgramID() == program_id) { + iter->processTSTransform(trans); + } + } + } + + // Is this a command packet? If so, its not necessarily associate + // with one particular substream. Just give it to the command + // packet handler and then move on. + if (4 == payload_type) { + processCommandPacket(pb); + goto process_next_packet; + } + } + + // If we got to here, then we are a normal packet. Find (or allocate) + // the substream we belong to and send the packet off to be processed. + substream = substreams_.valueFor(ssrc); + if (substream == NULL) { + substream = new Substream(ssrc, omx_); + if (substream == NULL) { + ALOGE("Failed to allocate substream for SSRC 0x%08x", ssrc); + goto process_next_packet; + } + substreams_.add(ssrc, substream); + + if (foundTrans) { + substream->processTSTransform(trans); + } + } + + CHECK(substream != NULL); + + if (marker) { + // Start of a new TRTP payload for this substream. Extract the + // lower 32 bits of the timestamp and hand the buffer to the + // substream for processing. + uint32_t ts_lower = U32_AT(data + 4); + substream->processPayloadStart(data + 12, amt - 12, ts_lower); + } else { + // Continuation of an existing TRTP payload. Just hand it off to + // the substream for processing. + substream->processPayloadCont(data + 12, amt - 12); + } + +process_next_packet: + PacketBuffer::destroy(pb); + } // end of main processing while loop. +} + +void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) { + CHECK(NULL != pb); + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + + // verify that this packet meets the minimum length of a command packet + if (amt < 20) { + return; + } + + uint8_t trtp_version = data[12]; + uint8_t trtp_flags = data[13] & 0xF; + + if (1 != trtp_version) { + ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); + return; + } + + // calculate the start of the command payload + ssize_t offset = 18; + if (trtp_flags & 0x01) { + // timestamp is present (4 bytes) + offset += 4; + } + if (trtp_flags & 0x02) { + // transform is present (24 bytes) + offset += 24; + } + + // the packet must contain 2 bytes of command payload beyond the TRTP header + if (amt < offset + 2) { + return; + } + + uint16_t command_id = U16_AT(data + offset); + + switch (command_id) { + case TRTPControlPacket::kCommandNop: + break; + + case TRTPControlPacket::kCommandEOS: + case TRTPControlPacket::kCommandFlush: { + uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F; + ALOGI("*** %s flushing program_id=%d", + __PRETTY_FUNCTION__, program_id); + + Vector<uint32_t> substreams_to_remove; + for (size_t i = 0; i < substreams_.size(); ++i) { + sp<Substream> iter = substreams_.valueAt(i); + if (iter->getProgramID() == program_id) { + iter->shutdown(); + substreams_to_remove.add(iter->getSSRC()); + } + } + + for (size_t i = 0; i < substreams_to_remove.size(); ++i) { + substreams_.removeItem(substreams_to_remove[i]); + } + } break; + } +} + +bool AAH_RXPlayer::processGaps() { + // Deal with the current gap situation. Specifically... + // + // 1) If a new gap has shown up, send a retransmit request to the + // transmitter. + // 2) If a gap we were working on has had a packet in the middle or at + // the end filled in, send another retransmit request for the begining + // portion of the gap. TRTP was designed for LANs where packet + // re-ordering is very unlikely; so see the middle or end of a gap + // filled in before the begining is an almost certain indication that + // a retransmission packet was also dropped. + // 3) If we have been working on a gap for a while and it still has not + // been filled in, send another retransmit request. + // 4) If the are no more gaps in the ring, clear the current_gap_status_ + // flag to indicate that all is well again. + + // Start by fetching the active gap status. + SeqNoGap gap; + bool send_retransmit_request = false; + bool ret_val = false; + GapStatus gap_status; + if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) { + // Note: checking for a change in the end sequence number should cover + // moving on to an entirely new gap for case #1 as well as resending the + // begining of a gap range for case #2. + send_retransmit_request = (kGS_NoGap == current_gap_status_) || + (current_gap_.end_seq_ != gap.end_seq_); + + // If this is the same gap we have been working on, and it has timed + // out, then check to see if our substreams are about to underflow. If + // so, instead of sending another retransmit request, just give up on + // this gap and move on. + if (!send_retransmit_request && + (kGS_NoGap != current_gap_status_) && + (0 == computeNextGapRetransmitTimeout())) { + + // If out current gap is the fast-start gap, don't bother to skip it + // because substreams look like the are about to underflow. + if ((kGS_FastStartGap != gap_status) || + (current_gap_.end_seq_ != gap.end_seq_)) { + for (size_t i = 0; i < substreams_.size(); ++i) { + if (substreams_.valueAt(i)->isAboutToUnderflow()) { + ALOGV("About to underflow, giving up on gap [%hu, %hu]", + gap.start_seq_, gap.end_seq_); + ring_buffer_.processNAK(); + current_gap_status_ = kGS_NoGap; + return true; + } + } + } + + // Looks like no one is about to underflow. Just go ahead and send + // the request. + send_retransmit_request = true; + } + } else { + current_gap_status_ = kGS_NoGap; + } + + if (send_retransmit_request) { + // If we have been working on a fast start, and it is still not filled + // in, even after the extended retransmit time out, give up and skip it. + // The system should fall back into its normal slow-start behavior. + if ((kGS_FastStartGap == current_gap_status_) && + (current_gap_.end_seq_ == gap.end_seq_)) { + ALOGV("Fast start is taking forever; giving up."); + ring_buffer_.processNAK(); + current_gap_status_ = kGS_NoGap; + return true; + } + + // Send the request. + RetransRequest req; + uint32_t magic = (kGS_FastStartGap == gap_status) + ? kFastStartRequestMagic + : kRetransRequestMagic; + req.magic_ = htonl(magic); + req.mcast_ip_ = listen_addr_.sin_addr.s_addr; + req.mcast_port_ = listen_addr_.sin_port; + req.start_seq_ = htons(gap.start_seq_); + req.end_seq_ = htons(gap.end_seq_); + + { + uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr); + uint16_t p = ntohs(transmitter_addr_.sin_port); + ALOGV("Sending to transmitter %u.%u.%u.%u:%hu", + ((a >> 24) & 0xFF), + ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), + ( a & 0xFF), + p); + } + + int res = sendto(sock_fd_, &req, sizeof(req), 0, + reinterpret_cast<struct sockaddr*>(&transmitter_addr_), + sizeof(transmitter_addr_)); + if (res < 0) { + ALOGE("Error when sending retransmit request (%d)", errno); + } else { + ALOGV("%s request for range [%hu, %hu] sent", + (kGS_FastStartGap == gap_status) ? "Fast Start" + : "Retransmit", + gap.start_seq_, gap.end_seq_); + } + + // Update the current gap info. + current_gap_ = gap; + current_gap_status_ = gap_status; + next_retrans_req_time_ = monotonicUSecNow() + + ((kGS_FastStartGap == current_gap_status_) + ? kFastStartTimeoutUSec + : kGapRerequestTimeoutUSec); + } + + return false; +} + +// Compute when its time to send the next gap retransmission in milliseconds. +// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit +// right now. +int AAH_RXPlayer::computeNextGapRetransmitTimeout() { + if (kGS_NoGap == current_gap_status_) { + return -1; + } + + int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow(); + + timeout_delta /= 1000; + if (timeout_delta <= 0) { + return 0; + } + + return static_cast<uint32_t>(timeout_delta); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp new file mode 100644 index 0000000..779405e --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include "aah_rx_player.h" + +namespace android { + +AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) { + capacity_ = capacity; + rd_ = wr_ = 0; + ring_ = new PacketBuffer*[capacity]; + memset(ring_, 0, sizeof(PacketBuffer*) * capacity); + reset(); +} + +AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() { + reset(); + delete[] ring_; +} + +void AAH_RXPlayer::RXRingBuffer::reset() { + AutoMutex lock(&lock_); + + if (NULL != ring_) { + while (rd_ != wr_) { + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + PacketBuffer::destroy(ring_[rd_]); + ring_[rd_] = NULL; + } + rd_ = (rd_ + 1) % capacity_; + } + } + + rd_ = wr_ = 0; + rd_seq_known_ = false; + waiting_for_fast_start_ = true; + fetched_first_packet_ = false; + rtp_activity_timeout_valid_ = false; +} + +bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf, + uint16_t seq) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != buf); + + rtp_activity_timeout_valid_ = true; + rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec; + + // If the ring buffer is totally reset (we have never received a single + // payload) then we don't know the rd sequence number and this should be + // simple. We just store the payload, advance the wr pointer and record the + // initial sequence number. + if (!rd_seq_known_) { + CHECK(rd_ == wr_); + CHECK(NULL == ring_[wr_]); + CHECK(wr_ < capacity_); + + ring_[wr_] = buf; + wr_ = (wr_ + 1) % capacity_; + rd_seq_ = seq; + rd_seq_known_ = true; + return true; + } + + // Compute the seqence number of this payload and of the write pointer, + // normalized around the read pointer. IOW - transform the payload seq no + // and the wr pointer seq no into a space where the rd pointer seq no is + // zero. This will define 4 cases we can consider... + // + // 1) norm_seq == norm_wr_seq + // This payload is contiguous with the last. All is good. + // + // 2) ((norm_seq < norm_wr_seq) && (norm_seq >= norm_rd_seq) + // aka ((norm_seq < norm_wr_seq) && (norm_seq >= 0) + // This payload is in the past, in the unprocessed region of the ring + // buffer. It is probably a retransmit intended to fill in a dropped + // payload; it may be a duplicate. + // + // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0 + // This payload is in the past compared to the write pointer (or so very + // far in the future that it has wrapped the seq no space), but not in + // the unprocessed region of the ring buffer. This could be a duplicate + // retransmit; we just drop these payloads unless we are waiting for our + // first fast start packet. If we are waiting for fast start, than this + // packet is probably the first packet of the fast start retransmission. + // If it will fit in the buffer, back up the read pointer to its position + // and clear the fast start flag, otherwise just drop it. + // + // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0 + // This payload which is ahead of the next write pointer. This indicates + // that we have missed some payloads and need to request a retransmit. + // If norm_seq >= (capacity - 1), then the gap is so large that it would + // overflow the ring buffer and we should probably start to panic. + + uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_); + uint16_t norm_seq = seq - rd_seq_; + + // Check for overflow first. + if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) { + ALOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu]," + " seq = %hu", capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq); + PacketBuffer::destroy(buf); + return false; + } + + // Check for case #1 + if (norm_seq == norm_wr_seq) { + CHECK(wr_ < capacity_); + CHECK(NULL == ring_[wr_]); + + ring_[wr_] = buf; + wr_ = (wr_ + 1) % capacity_; + + CHECK(wr_ != rd_); + return true; + } + + // Check case #2 + uint32_t ring_pos = (rd_ + norm_seq) % capacity_; + if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) { + // Do we already have a payload for this slot? If so, then this looks + // like a duplicate retransmit. Just ignore it. + if (NULL != ring_[ring_pos]) { + ALOGD("RXed duplicate retransmit, seq = %hu", seq); + PacketBuffer::destroy(buf); + } else { + // Looks like we were missing this payload. Go ahead and store it. + ring_[ring_pos] = buf; + } + + return true; + } + + // Check case #3 + if ((norm_seq - norm_wr_seq) & 0x8000) { + if (!waiting_for_fast_start_) { + ALOGD("RXed duplicate retransmit from before rd pointer, seq = %hu", + seq); + PacketBuffer::destroy(buf); + } else { + // Looks like a fast start fill-in. Go ahead and store it, assuming + // that we can fit it in the buffer. + uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq) + + (rd_seq_ - seq); + + if (implied_ring_size >= (capacity_ - 1)) { + ALOGD("RXed what looks like a fast start packet (seq = %hu)," + " but packet is too far in the past to fit into the ring" + " buffer. Dropping.", seq); + PacketBuffer::destroy(buf); + } else { + ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_; + rd_seq_ = seq; + rd_ = ring_pos; + waiting_for_fast_start_ = false; + + CHECK(ring_pos < capacity_); + CHECK(NULL == ring_[ring_pos]); + ring_[ring_pos] = buf; + } + + } + return true; + } + + // Must be in case #4 with no overflow. This packet fits in the current + // ring buffer, but is discontiuguous. Advance the write pointer leaving a + // gap behind. + uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_; + ALOGD("Drop detected; %u packets, seq_range [%hu, %hu]", + gap_len, + rd_seq_ + norm_wr_seq, + rd_seq_ + norm_wr_seq + gap_len - 1); + + CHECK(NULL == ring_[ring_pos]); + ring_[ring_pos] = buf; + wr_ = (ring_pos + 1) % capacity_; + CHECK(wr_ != rd_); + + return true; +} + +AAH_RXPlayer::PacketBuffer* +AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != is_discon); + + // If the read seqence number is not known, then this ring buffer has not + // received a packet since being reset and there cannot be any packets to + // return. If we are still waiting for the first fast start packet to show + // up, we don't want to let any buffer be consumed yet because we expect to + // see a packet before the initial read sequence number show up shortly. + if (!rd_seq_known_ || waiting_for_fast_start_) { + *is_discon = false; + return NULL; + } + + PacketBuffer* ret = NULL; + *is_discon = !fetched_first_packet_; + + while ((rd_ != wr_) && (NULL == ret)) { + CHECK(rd_ < capacity_); + + // If we hit a gap, stall and do not advance the read pointer. Let the + // higher level code deal with requesting retries and/or deciding to + // skip the current gap. + ret = ring_[rd_]; + if (NULL == ret) { + break; + } + + ring_[rd_] = NULL; + rd_ = (rd_ + 1) % capacity_; + ++rd_seq_; + } + + if (NULL != ret) { + fetched_first_packet_ = true; + } + + return ret; +} + +AAH_RXPlayer::GapStatus +AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != gap); + + // If the read seqence number is not known, then this ring buffer has not + // received a packet since being reset and there cannot be any gaps. + if (!rd_seq_known_) { + return kGS_NoGap; + } + + // If we are waiting for fast start, then the current gap is a fast start + // gap and it includes all packets before the read sequence number. + if (waiting_for_fast_start_) { + gap->start_seq_ = + gap->end_seq_ = rd_seq_ - 1; + return kGS_FastStartGap; + } + + // If rd == wr, then the buffer is empty and there cannot be any gaps. + if (rd_ == wr_) { + return kGS_NoGap; + } + + // If rd_ is currently pointing at an unprocessed packet, then there is no + // current gap. + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + return kGS_NoGap; + } + + // Looks like there must be a gap here. The start of the gap is the current + // rd sequence number, all we need to do now is determine its length in + // order to compute the end sequence number. + gap->start_seq_ = rd_seq_; + uint16_t end = rd_seq_; + uint32_t tmp = (rd_ + 1) % capacity_; + while ((tmp != wr_) && (NULL == ring_[tmp])) { + ++end; + tmp = (tmp + 1) % capacity_; + } + gap->end_seq_ = end; + + return kGS_NormalGap; +} + +void AAH_RXPlayer::RXRingBuffer::processNAK(const SeqNoGap* nak) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + + // If we were waiting for our first fast start fill-in packet, and we + // received a NAK, then apparantly we are not getting our fast start. Just + // clear the waiting flag and go back to normal behavior. + if (waiting_for_fast_start_) { + waiting_for_fast_start_ = false; + } + + // If we have not received a packet since last reset, or there is no data in + // the ring, then there is nothing to skip. + if ((!rd_seq_known_) || (rd_ == wr_)) { + return; + } + + // If rd_ is currently pointing at an unprocessed packet, then there is no + // gap to skip. + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + return; + } + + // Looks like there must be a gap here. Advance rd until we have passed + // over the portion of it indicated by nak (or all of the gap if nak is + // NULL). Then reset fetched_first_packet_ so that the next read will show + // up as being discontiguous. + uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1; + while ((rd_ != wr_) && + (NULL == ring_[rd_]) && + ((NULL == nak) || (seq_after_gap != rd_seq_))) { + rd_ = (rd_ + 1) % capacity_; + ++rd_seq_; + } + fetched_first_packet_ = false; +} + +int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() { + AutoMutex lock(&lock_); + + if (!rtp_activity_timeout_valid_) { + return -1; + } + + uint64_t now = monotonicUSecNow(); + if (rtp_activity_timeout_ <= now) { + return 0; + } + + return (rtp_activity_timeout_ - now) / 1000; +} + +AAH_RXPlayer::PacketBuffer* +AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) { + if (length <= 0) { + return NULL; + } + + uint32_t alloc_len = sizeof(PacketBuffer) + length; + PacketBuffer* ret = reinterpret_cast<PacketBuffer*>( + new uint8_t[alloc_len]); + + if (NULL != ret) { + ret->length_ = length; + } + + return ret; +} + +void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) { + uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb); + delete[] kill_me; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp new file mode 100644 index 0000000..18b0e2b --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_substream.cpp @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include <include/avc_utils.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXCodec.h> +#include <media/stagefright/Utils.h> + +#include "aah_rx_player.h" +#include "aah_tx_packet.h" + +inline uint32_t min(uint32_t a, uint32_t b) { + return (a < b ? a : b); +} + +namespace android { + +int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold = + 50ull * 1000; + +AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) { + ssrc_ = ssrc; + substream_details_known_ = false; + buffer_in_progress_ = NULL; + status_ = OK; + codec_mime_type_ = ""; + + decoder_ = new AAH_DecoderPump(omx); + if (decoder_ == NULL) { + ALOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__); + } + if (OK != decoder_->initCheck()) { + ALOGE("%s failed to initialize decoder pump!", __PRETTY_FUNCTION__); + } + + // cleanupBufferInProgress will reset most of the internal state variables. + // Just need to make sure that buffer_in_progress_ is NULL before calling. + cleanupBufferInProgress(); +} + +AAH_RXPlayer::Substream::~Substream() { + shutdown(); +} + +void AAH_RXPlayer::Substream::shutdown() { + substream_meta_ = NULL; + status_ = OK; + cleanupBufferInProgress(); + cleanupDecoder(); +} + +void AAH_RXPlayer::Substream::cleanupBufferInProgress() { + if (NULL != buffer_in_progress_) { + buffer_in_progress_->release(); + buffer_in_progress_ = NULL; + } + + expected_buffer_size_ = 0; + buffer_filled_ = 0; + waiting_for_rap_ = true; + + aux_data_in_progress_.clear(); + aux_data_expected_size_ = 0; +} + +void AAH_RXPlayer::Substream::cleanupDecoder() { + if (decoder_ != NULL) { + decoder_->shutdown(); + } +} + +bool AAH_RXPlayer::Substream::shouldAbort(const char* log_tag) { + // If we have already encountered a fatal error, do nothing. We are just + // waiting for our owner to shut us down now. + if (OK != status_) { + ALOGV("Skipping %s, substream has encountered fatal error (%d).", + log_tag, status_); + return true; + } + + return false; +} + +void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf, + uint32_t amt, + int32_t ts_lower) { + uint32_t min_length = 6; + + if (shouldAbort(__PRETTY_FUNCTION__)) { + return; + } + + // Do we have a buffer in progress already? If so, abort the buffer. In + // theory, this should never happen. If there were a discontinutity in the + // stream, the discon in the seq_nos at the RTP level should have already + // triggered a cleanup of the buffer in progress. To see a problem at this + // level is an indication either of a bug in the transmitter, or some form + // of terrible corruption/tampering on the wire. + if (NULL != buffer_in_progress_) { + ALOGE("processPayloadStart is aborting payload already in progress."); + cleanupBufferInProgress(); + } + + // Parse enough of the header to know where we stand. Since this is a + // payload start, it should begin with a TRTP header which has to be at + // least 6 bytes long. + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP header (len = %u)", + amt); + return; + } + + // Check the TRTP version number. + if (0x01 != buf[0]) { + ALOGV("Unexpected TRTP version (%u) in header. Expected %u.", + buf[0], 1); + return; + } + + // Extract the substream type field and make sure its one we understand (and + // one that does not conflict with any previously received substream type. + uint8_t header_type = (buf[1] >> 4) & 0xF; + switch (header_type) { + case TRTPPacket::kHeaderTypeAudio: + // Audio, yay! Just break. We understand audio payloads. + break; + case TRTPPacket::kHeaderTypeVideo: + ALOGV("RXed packet with unhandled TRTP header type (Video)."); + return; + case TRTPPacket::kHeaderTypeSubpicture: + ALOGV("RXed packet with unhandled TRTP header type (Subpicture)."); + return; + case TRTPPacket::kHeaderTypeControl: + ALOGV("RXed packet with unhandled TRTP header type (Control)."); + return; + default: + ALOGV("RXed packet with unhandled TRTP header type (%u).", + header_type); + return; + } + + if (substream_details_known_ && (header_type != substream_type_)) { + ALOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does" + " not match previously received header type (%u)", + ssrc_, header_type, substream_type_); + return; + } + + // Check the flags to see if there is another 32 bits of timestamp present. + uint32_t trtp_header_len = 6; + bool ts_valid = buf[1] & TRTPPacket::kFlag_TSValid; + if (ts_valid) { + min_length += 4; + trtp_header_len += 4; + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP timestamp" + " (len = %u)", amt); + return; + } + } + + // Extract the TRTP length field and sanity check it. + uint32_t trtp_len = U32_AT(buf + 2); + if (trtp_len < min_length) { + ALOGV("TRTP length (%u) is too short to be valid. Must be at least %u" + " bytes.", trtp_len, min_length); + return; + } + + // Extract the rest of the timestamp field if valid. + int64_t ts = 0; + uint32_t parse_offset = 6; + if (ts_valid) { + uint32_t ts_upper = U32_AT(buf + parse_offset); + parse_offset += 4; + ts = (static_cast<int64_t>(ts_upper) << 32) | ts_lower; + } + + // Check the flags to see if there is another 24 bytes of timestamp + // transformation present. + if (buf[1] & TRTPPacket::kFlag_TSTransformPresent) { + min_length += 24; + parse_offset += 24; + trtp_header_len += 24; + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP timestamp" + " transformation (len = %u)", amt); + return; + } + } + + // TODO : break the parsing into individual parsers for the different + // payload types (audio, video, etc). + // + // At this point in time, we know that this is audio. Go ahead and parse + // the basic header, check the codec type, and find the payload portion of + // the packet. + min_length += 3; + if (trtp_len < min_length) { + ALOGV("TRTP length (%u) is too short to be a valid audio payload. Must" + " be at least %u bytes.", trtp_len, min_length); + return; + } + + if (amt < min_length) { + ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain" + " entire TRTP header. TRTP does not currently support" + " fragmenting TRTP headers across RTP payloads", amt); + return; + } + + uint8_t codec_type = buf[parse_offset ]; + uint8_t flags = buf[parse_offset + 1]; + uint8_t volume = buf[parse_offset + 2]; + parse_offset += 3; + trtp_header_len += 3; + + if (!setupSubstreamType(header_type, codec_type)) { + return; + } + + if (decoder_ != NULL) { + decoder_->setRenderVolume(volume); + } + + if (waiting_for_rap_ && !(flags & TRTPAudioPacket::kFlag_RandomAccessPoint)) { + ALOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP."); + return; + } + + // Check for the presence of codec aux data. + if (flags & TRTPAudioPacket::kFlag_AuxLengthPresent) { + min_length += 4; + trtp_header_len += 4; + + if (trtp_len < min_length) { + ALOGV("TRTP length (%u) is too short to be a valid audio payload. " + "Must be at least %u bytes.", trtp_len, min_length); + return; + } + + if (amt < min_length) { + ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain" + " entire TRTP header. TRTP does not currently support" + " fragmenting TRTP headers across RTP payloads", amt); + return; + } + + aux_data_expected_size_ = U32_AT(buf + parse_offset); + aux_data_in_progress_.clear(); + if (aux_data_in_progress_.capacity() < aux_data_expected_size_) { + aux_data_in_progress_.setCapacity(aux_data_expected_size_); + } + } else { + aux_data_expected_size_ = 0; + } + + if ((aux_data_expected_size_ + trtp_header_len) > trtp_len) { + ALOGV("Expected codec aux data length (%u) and TRTP header overhead" + " (%u) too large for total TRTP payload length (%u).", + aux_data_expected_size_, trtp_header_len, trtp_len); + return; + } + + // OK - everything left is just payload. Compute the payload size, start + // the buffer in progress and pack as much payload as we can into it. If + // the payload is finished once we are done, go ahead and send the payload + // to the decoder. + expected_buffer_size_ = trtp_len + - trtp_header_len + - aux_data_expected_size_; + if (!expected_buffer_size_) { + ALOGV("Dropping TRTP Audio Payload with 0 Access Unit length"); + return; + } + + CHECK(amt >= trtp_header_len); + uint32_t todo = amt - trtp_header_len; + if ((expected_buffer_size_ + aux_data_expected_size_) < todo) { + ALOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;" + " dropping payload.", todo, + expected_buffer_size_ + aux_data_expected_size_); + return; + } + + buffer_filled_ = 0; + buffer_in_progress_ = new MediaBuffer(expected_buffer_size_); + if ((NULL == buffer_in_progress_) || + (NULL == buffer_in_progress_->data())) { + ALOGV("Failed to allocate MediaBuffer of length %u", + expected_buffer_size_); + cleanupBufferInProgress(); + return; + } + + sp<MetaData> meta = buffer_in_progress_->meta_data(); + if (meta == NULL) { + ALOGV("Missing metadata structure in allocated MediaBuffer; dropping" + " payload"); + cleanupBufferInProgress(); + return; + } + + meta->setCString(kKeyMIMEType, codec_mime_type_); + if (ts_valid) { + meta->setInt64(kKeyTime, ts); + } + + // Skip over the header we have already extracted. + amt -= trtp_header_len; + buf += trtp_header_len; + + // Extract as much of the expected aux data as we can. + todo = min(aux_data_expected_size_, amt); + if (todo) { + aux_data_in_progress_.appendArray(buf, todo); + buf += todo; + amt -= todo; + } + + // Extract as much of the expected payload as we can. + todo = min(expected_buffer_size_, amt); + if (todo > 0) { + uint8_t* tgt = + reinterpret_cast<uint8_t*>(buffer_in_progress_->data()); + memcpy(tgt, buf, todo); + buffer_filled_ = amt; + buf += todo; + amt -= todo; + } + + if (buffer_filled_ >= expected_buffer_size_) { + processCompletedBuffer(); + } +} + +void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf, + uint32_t amt) { + if (shouldAbort(__PRETTY_FUNCTION__)) { + return; + } + + if (NULL == buffer_in_progress_) { + ALOGV("TRTP Receiver skipping payload continuation; no buffer currently" + " in progress."); + return; + } + + CHECK(aux_data_in_progress_.size() <= aux_data_expected_size_); + uint32_t aux_left = aux_data_expected_size_ - aux_data_in_progress_.size(); + if (aux_left) { + uint32_t todo = min(aux_left, amt); + aux_data_in_progress_.appendArray(buf, todo); + amt -= todo; + buf += todo; + + if (!amt) + return; + } + + CHECK(buffer_filled_ < expected_buffer_size_); + uint32_t buffer_left = expected_buffer_size_ - buffer_filled_; + if (amt > buffer_left) { + ALOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;" + " dropping payload.", amt, buffer_left); + cleanupBufferInProgress(); + return; + } + + if (amt > 0) { + uint8_t* tgt = + reinterpret_cast<uint8_t*>(buffer_in_progress_->data()); + memcpy(tgt + buffer_filled_, buf, amt); + buffer_filled_ += amt; + } + + if (buffer_filled_ >= expected_buffer_size_) { + processCompletedBuffer(); + } +} + +void AAH_RXPlayer::Substream::processCompletedBuffer() { + status_t res; + + CHECK(NULL != buffer_in_progress_); + + if (decoder_ == NULL) { + ALOGV("Dropping complete buffer, no decoder pump allocated"); + goto bailout; + } + + // Make sure our metadata used to initialize the decoder has been properly + // set up. + if (!setupSubstreamMeta()) + goto bailout; + + // If our decoder has not be set up, do so now. + res = decoder_->init(substream_meta_); + if (OK != res) { + ALOGE("Failed to init decoder (res = %d)", res); + cleanupDecoder(); + substream_meta_ = NULL; + goto bailout; + } + + // Queue the payload for decode. + res = decoder_->queueForDecode(buffer_in_progress_); + + if (res != OK) { + ALOGD("Failed to queue payload for decode, resetting decoder pump!" + " (res = %d)", res); + status_ = res; + cleanupDecoder(); + cleanupBufferInProgress(); + } + + // NULL out buffer_in_progress before calling the cleanup helper. + // + // MediaBuffers use something of a hybrid ref-counting pattern which prevent + // the AAH_DecoderPump's input queue from adding their own reference to the + // MediaBuffer. MediaBuffers start life with a reference count of 0, as + // well as an observer which starts as NULL. Before being given an + // observer, the ref count cannot be allowed to become non-zero as it will + // cause calls to release() to assert. Basically, before a MediaBuffer has + // an observer, they behave like non-ref counted obects where release() + // serves the roll of delete. After a MediaBuffer has an observer, they + // become more like ref counted objects where add ref and release can be + // used, and when the ref count hits zero, the MediaBuffer is handed off to + // the observer. + // + // Given all of this, when we give the buffer to the decoder pump to wait in + // the to-be-processed queue, the decoder cannot add a ref to the buffer as + // it would in a traditional ref counting system. Instead it needs to + // "steal" the non-existent ref. In the case of queue failure, we need to + // make certain to release this non-existent reference so that the buffer is + // cleaned up during the cleanupBufferInProgress helper. In the case of a + // successful queue operation, we need to make certain that the + // cleanupBufferInProgress helper does not release the buffer since it needs + // to remain alive in the queue. We acomplish this by NULLing out the + // buffer pointer before calling the cleanup helper. + buffer_in_progress_ = NULL; + +bailout: + cleanupBufferInProgress(); +} + +bool AAH_RXPlayer::Substream::setupSubstreamMeta() { + switch (codec_type_) { + case TRTPAudioPacket::kCodecMPEG1Audio: + codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_MPEG; + return setupMP3SubstreamMeta(); + + case TRTPAudioPacket::kCodecAACAudio: + codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_AAC; + return setupAACSubstreamMeta(); + + default: + ALOGV("Failed to setup substream metadata for unsupported codec" + " type (%u)", codec_type_); + break; + } + + return false; +} + +bool AAH_RXPlayer::Substream::setupMP3SubstreamMeta() { + const uint8_t* buffer_data = NULL; + int sample_rate; + int channel_count; + size_t frame_size; + status_t res; + + buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data()); + if (buffer_in_progress_->size() < 4) { + ALOGV("MP3 payload too short to contain header, dropping payload."); + return false; + } + + // Extract the channel count and the sample rate from the MP3 header. The + // stagefright MP3 requires that these be delivered before decoing can + // begin. + if (!GetMPEGAudioFrameSize(U32_AT(buffer_data), + &frame_size, + &sample_rate, + &channel_count, + NULL, + NULL)) { + ALOGV("Failed to parse MP3 header in payload, droping payload."); + return false; + } + + + // Make sure that our substream metadata is set up properly. If there has + // been a format change, be sure to reset the underlying decoder. In + // stagefright, it seems like the only way to do this is to destroy and + // recreate the decoder. + if (substream_meta_ == NULL) { + substream_meta_ = new MetaData(); + + if (substream_meta_ == NULL) { + ALOGE("Failed to allocate MetaData structure for MP3 substream"); + return false; + } + + substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + substream_meta_->setInt32 (kKeyChannelCount, channel_count); + substream_meta_->setInt32 (kKeySampleRate, sample_rate); + } else { + int32_t prev_sample_rate; + int32_t prev_channel_count; + substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate); + substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count); + + if ((prev_channel_count != channel_count) || + (prev_sample_rate != sample_rate)) { + ALOGW("MP3 format change detected, forcing decoder reset."); + cleanupDecoder(); + + substream_meta_->setInt32(kKeyChannelCount, channel_count); + substream_meta_->setInt32(kKeySampleRate, sample_rate); + } + } + + return true; +} + +bool AAH_RXPlayer::Substream::setupAACSubstreamMeta() { + int32_t sample_rate, channel_cnt; + static const size_t overhead = sizeof(sample_rate) + + sizeof(channel_cnt); + + if (aux_data_in_progress_.size() < overhead) { + ALOGE("Not enough aux data (%u) to initialize AAC substream decoder", + aux_data_in_progress_.size()); + return false; + } + + const uint8_t* aux_data = aux_data_in_progress_.array(); + size_t aux_data_size = aux_data_in_progress_.size(); + sample_rate = U32_AT(aux_data); + channel_cnt = U32_AT(aux_data + sizeof(sample_rate)); + + const uint8_t* esds_data = NULL; + size_t esds_data_size = 0; + if (aux_data_size > overhead) { + esds_data = aux_data + overhead; + esds_data_size = aux_data_size - overhead; + } + + // Do we already have metadata? If so, has it changed at all? If not, then + // there should be nothing else to do. Otherwise, release our old stream + // metadata and make new metadata. + if (substream_meta_ != NULL) { + uint32_t type; + const void* data; + size_t size; + int32_t prev_sample_rate; + int32_t prev_channel_count; + bool res; + + res = substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate); + CHECK(res); + res = substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count); + CHECK(res); + + // If nothing has changed about the codec aux data (esds, sample rate, + // channel count), then we can just do nothing and get out. Otherwise, + // we will need to reset the decoder and make a new metadata object to + // deal with the format change. + bool hasData = (esds_data != NULL); + bool hadData = substream_meta_->findData(kKeyESDS, &type, &data, &size); + bool esds_change = (hadData != hasData); + + if (!esds_change && hasData) + esds_change = ((size != esds_data_size) || + memcmp(data, esds_data, size)); + + if (!esds_change && + (prev_sample_rate == sample_rate) && + (prev_channel_count == channel_cnt)) { + return true; // no change, just get out. + } + + ALOGW("AAC format change detected, forcing decoder reset."); + cleanupDecoder(); + substream_meta_ = NULL; + } + + CHECK(substream_meta_ == NULL); + + substream_meta_ = new MetaData(); + if (substream_meta_ == NULL) { + ALOGE("Failed to allocate MetaData structure for AAC substream"); + return false; + } + + substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); + substream_meta_->setInt32 (kKeySampleRate, sample_rate); + substream_meta_->setInt32 (kKeyChannelCount, channel_cnt); + + if (esds_data) { + substream_meta_->setData(kKeyESDS, kTypeESDS, + esds_data, esds_data_size); + } + + return true; +} + +void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) { + if (decoder_ != NULL) { + decoder_->setRenderTSTransform(trans); + } +} + +bool AAH_RXPlayer::Substream::isAboutToUnderflow() { + if (decoder_ == NULL) { + return false; + } + + return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold); +} + +bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type, + uint8_t codec_type) { + // Sanity check the codec type. Right now we only support MP3 and AAC. + // Also check for conflicts with previously delivered codec types. + if (substream_details_known_) { + if (codec_type != codec_type_) { + ALOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does" + " not match previously received codec type (%u)", + ssrc_, codec_type, codec_type_); + return false; + } + + return true; + } + + switch (codec_type) { + // MP3 and AAC are all we support right now. + case TRTPAudioPacket::kCodecMPEG1Audio: + case TRTPAudioPacket::kCodecAACAudio: + break; + + default: + ALOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported" + " codec type (%u)", ssrc_, codec_type); + return false; + } + + substream_type_ = substream_type; + codec_type_ = codec_type; + substream_details_known_ = true; + + return true; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp new file mode 100644 index 0000000..4cd6e47 --- /dev/null +++ b/media/libaah_rtp/aah_tx_packet.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <string.h> + +#include <media/stagefright/foundation/ADebug.h> + +#include "aah_tx_packet.h" + +namespace android { + +const int TRTPPacket::kRTPHeaderLen; +const uint32_t TRTPPacket::kTRTPEpochMask; + +TRTPPacket::~TRTPPacket() { + delete mPacket; +} + +/*** TRTP packet properties ***/ + +void TRTPPacket::setSeqNumber(uint16_t val) { + mSeqNumber = val; + + if (mIsPacked) { + const int kTRTPSeqNumberOffset = 2; + uint16_t* buf = reinterpret_cast<uint16_t*>( + mPacket + kTRTPSeqNumberOffset); + *buf = htons(mSeqNumber); + } +} + +uint16_t TRTPPacket::getSeqNumber() const { + return mSeqNumber; +} + +void TRTPPacket::setPTS(int64_t val) { + CHECK(!mIsPacked); + mPTS = val; + mPTSValid = true; +} + +int64_t TRTPPacket::getPTS() const { + return mPTS; +} + +void TRTPPacket::setEpoch(uint32_t val) { + mEpoch = val; + + if (mIsPacked) { + const int kTRTPEpochOffset = 8; + uint32_t* buf = reinterpret_cast<uint32_t*>( + mPacket + kTRTPEpochOffset); + uint32_t val = ntohl(*buf); + val &= ~(kTRTPEpochMask << kTRTPEpochShift); + val |= (mEpoch & kTRTPEpochMask) << kTRTPEpochShift; + *buf = htonl(val); + } +} + +void TRTPPacket::setProgramID(uint16_t val) { + CHECK(!mIsPacked); + mProgramID = val; +} + +void TRTPPacket::setSubstreamID(uint16_t val) { + CHECK(!mIsPacked); + mSubstreamID = val; +} + + +void TRTPPacket::setClockTransform(const LinearTransform& trans) { + CHECK(!mIsPacked); + mClockTranform = trans; + mClockTranformValid = true; +} + +uint8_t* TRTPPacket::getPacket() const { + CHECK(mIsPacked); + return mPacket; +} + +int TRTPPacket::getPacketLen() const { + CHECK(mIsPacked); + return mPacketLen; +} + +void TRTPPacket::setExpireTime(nsecs_t val) { + CHECK(!mIsPacked); + mExpireTime = val; +} + +nsecs_t TRTPPacket::getExpireTime() const { + return mExpireTime; +} + +/*** TRTP audio packet properties ***/ + +void TRTPAudioPacket::setCodecType(TRTPAudioCodecType val) { + CHECK(!mIsPacked); + mCodecType = val; +} + +void TRTPAudioPacket::setRandomAccessPoint(bool val) { + CHECK(!mIsPacked); + mRandomAccessPoint = val; +} + +void TRTPAudioPacket::setDropable(bool val) { + CHECK(!mIsPacked); + mDropable = val; +} + +void TRTPAudioPacket::setDiscontinuity(bool val) { + CHECK(!mIsPacked); + mDiscontinuity = val; +} + +void TRTPAudioPacket::setEndOfStream(bool val) { + CHECK(!mIsPacked); + mEndOfStream = val; +} + +void TRTPAudioPacket::setVolume(uint8_t val) { + CHECK(!mIsPacked); + mVolume = val; +} + +void TRTPAudioPacket::setAccessUnitData(const void* data, size_t len) { + CHECK(!mIsPacked); + mAccessUnitData = data; + mAccessUnitLen = len; +} + +void TRTPAudioPacket::setAuxData(const void* data, size_t len) { + CHECK(!mIsPacked); + mAuxData = data; + mAuxDataLen = len; +} + +/*** TRTP control packet properties ***/ + +void TRTPControlPacket::setCommandID(TRTPCommandID val) { + CHECK(!mIsPacked); + mCommandID = val; +} + +/*** TRTP packet serializers ***/ + +void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) { + *buf = val; + buf++; +} + +void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) { + *reinterpret_cast<uint16_t*>(buf) = htons(val); + buf += 2; +} + +void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) { + *reinterpret_cast<uint32_t*>(buf) = htonl(val); + buf += 4; +} + +void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) { + buf[0] = static_cast<uint8_t>(val >> 56); + buf[1] = static_cast<uint8_t>(val >> 48); + buf[2] = static_cast<uint8_t>(val >> 40); + buf[3] = static_cast<uint8_t>(val >> 32); + buf[4] = static_cast<uint8_t>(val >> 24); + buf[5] = static_cast<uint8_t>(val >> 16); + buf[6] = static_cast<uint8_t>(val >> 8); + buf[7] = static_cast<uint8_t>(val); + buf += 8; +} + +void TRTPPacket::writeTRTPHeader(uint8_t*& buf, + bool isFirstFragment, + int totalPacketLen) { + // RTP header + writeU8(buf, + ((mVersion & 0x03) << 6) | + (static_cast<int>(mPadding) << 5) | + (static_cast<int>(mExtension) << 4) | + (mCsrcCount & 0x0F)); + writeU8(buf, + (static_cast<int>(isFirstFragment) << 7) | + (mPayloadType & 0x7F)); + writeU16(buf, mSeqNumber); + if (isFirstFragment && mPTSValid) { + writeU32(buf, mPTS & 0xFFFFFFFF); + } else { + writeU32(buf, 0); + } + writeU32(buf, + ((mEpoch & kTRTPEpochMask) << kTRTPEpochShift) | + ((mProgramID & 0x1F) << 5) | + (mSubstreamID & 0x1F)); + + // TRTP header + writeU8(buf, mTRTPVersion); + writeU8(buf, + ((mTRTPHeaderType & 0x0F) << 4) | + (mClockTranformValid ? 0x02 : 0x00) | + (mPTSValid ? 0x01 : 0x00)); + writeU32(buf, totalPacketLen - kRTPHeaderLen); + if (mPTSValid) { + writeU32(buf, mPTS >> 32); + } + + if (mClockTranformValid) { + writeU64(buf, mClockTranform.a_zero); + writeU32(buf, mClockTranform.a_to_b_numer); + writeU32(buf, mClockTranform.a_to_b_denom); + writeU64(buf, mClockTranform.b_zero); + } +} + +bool TRTPAudioPacket::pack() { + if (mIsPacked) { + return false; + } + + int packetLen = kRTPHeaderLen + + mAuxDataLen + + mAccessUnitLen + + TRTPHeaderLen(); + + // TODO : support multiple fragments + const int kMaxUDPPayloadLen = 65507; + if (packetLen > kMaxUDPPayloadLen) { + return false; + } + + mPacket = new uint8_t[packetLen]; + if (!mPacket) { + return false; + } + + mPacketLen = packetLen; + + uint8_t* cur = mPacket; + bool hasAux = mAuxData && mAuxDataLen; + uint8_t flags = (static_cast<int>(hasAux) << 4) | + (static_cast<int>(mRandomAccessPoint) << 3) | + (static_cast<int>(mDropable) << 2) | + (static_cast<int>(mDiscontinuity) << 1) | + (static_cast<int>(mEndOfStream)); + + writeTRTPHeader(cur, true, packetLen); + writeU8(cur, mCodecType); + writeU8(cur, flags); + writeU8(cur, mVolume); + + if (hasAux) { + writeU32(cur, mAuxDataLen); + memcpy(cur, mAuxData, mAuxDataLen); + cur += mAuxDataLen; + } + + memcpy(cur, mAccessUnitData, mAccessUnitLen); + + mIsPacked = true; + return true; +} + +int TRTPPacket::TRTPHeaderLen() const { + // 6 bytes for version, payload type, flags and length. An additional 4 if + // there are upper timestamp bits present and another 24 if there is a clock + // transformation present. + return 6 + + (mClockTranformValid ? 24 : 0) + + (mPTSValid ? 4 : 0); +} + +int TRTPAudioPacket::TRTPHeaderLen() const { + // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader. 3 bytes for audio's + // codec type, flags and volume field. Another 5 bytes if the codec type is + // PCM and we are sending sample rate/channel count. as well as however long + // the aux data (if present) is. + + int pcmParamLength; + switch(mCodecType) { + case kCodecPCMBigEndian: + case kCodecPCMLittleEndian: + pcmParamLength = 5; + break; + + default: + pcmParamLength = 0; + break; + } + + + int auxDataLenField = (NULL != mAuxData) ? sizeof(uint32_t) : 0; + return TRTPPacket::TRTPHeaderLen() + + 3 + + auxDataLenField + + pcmParamLength; +} + +bool TRTPControlPacket::pack() { + if (mIsPacked) { + return false; + } + + // command packets contain a 2-byte command ID + int packetLen = kRTPHeaderLen + + TRTPHeaderLen() + + 2; + + mPacket = new uint8_t[packetLen]; + if (!mPacket) { + return false; + } + + mPacketLen = packetLen; + + uint8_t* cur = mPacket; + + writeTRTPHeader(cur, true, packetLen); + writeU16(cur, mCommandID); + + mIsPacked = true; + return true; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h new file mode 100644 index 0000000..7f78ea0 --- /dev/null +++ b/media/libaah_rtp/aah_tx_packet.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __AAH_TX_PACKET_H__ +#define __AAH_TX_PACKET_H__ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/LinearTransform.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> + +namespace android { + +class TRTPPacket : public RefBase { + public: + enum TRTPHeaderType { + kHeaderTypeAudio = 1, + kHeaderTypeVideo = 2, + kHeaderTypeSubpicture = 3, + kHeaderTypeControl = 4, + }; + + enum TRTPPayloadFlags { + kFlag_TSTransformPresent = 0x02, + kFlag_TSValid = 0x01, + }; + + protected: + TRTPPacket(TRTPHeaderType headerType) + : mIsPacked(false) + , mVersion(2) + , mPadding(false) + , mExtension(false) + , mCsrcCount(0) + , mPayloadType(100) + , mSeqNumber(0) + , mPTSValid(false) + , mPTS(0) + , mEpoch(0) + , mProgramID(0) + , mSubstreamID(0) + , mClockTranformValid(false) + , mTRTPVersion(1) + , mTRTPLength(0) + , mTRTPHeaderType(headerType) + , mPacket(NULL) + , mPacketLen(0) { } + + public: + virtual ~TRTPPacket(); + + void setSeqNumber(uint16_t val); + uint16_t getSeqNumber() const; + + void setPTS(int64_t val); + int64_t getPTS() const; + + void setEpoch(uint32_t val); + void setProgramID(uint16_t val); + void setSubstreamID(uint16_t val); + void setClockTransform(const LinearTransform& trans); + + uint8_t* getPacket() const; + int getPacketLen() const; + + void setExpireTime(nsecs_t val); + nsecs_t getExpireTime() const; + + virtual bool pack() = 0; + + // mask for the number of bits in a TRTP epoch + static const uint32_t kTRTPEpochMask = (1 << 22) - 1; + static const int kTRTPEpochShift = 10; + + protected: + static const int kRTPHeaderLen = 12; + virtual int TRTPHeaderLen() const; + + void writeTRTPHeader(uint8_t*& buf, + bool isFirstFragment, + int totalPacketLen); + + void writeU8(uint8_t*& buf, uint8_t val); + void writeU16(uint8_t*& buf, uint16_t val); + void writeU32(uint8_t*& buf, uint32_t val); + void writeU64(uint8_t*& buf, uint64_t val); + + bool mIsPacked; + + uint8_t mVersion; + bool mPadding; + bool mExtension; + uint8_t mCsrcCount; + uint8_t mPayloadType; + uint16_t mSeqNumber; + bool mPTSValid; + int64_t mPTS; + uint32_t mEpoch; + uint16_t mProgramID; + uint16_t mSubstreamID; + LinearTransform mClockTranform; + bool mClockTranformValid; + uint8_t mTRTPVersion; + uint32_t mTRTPLength; + TRTPHeaderType mTRTPHeaderType; + + uint8_t* mPacket; + int mPacketLen; + + nsecs_t mExpireTime; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPPacket); +}; + +class TRTPAudioPacket : public TRTPPacket { + public: + enum AudioPayloadFlags { + kFlag_AuxLengthPresent = 0x10, + kFlag_RandomAccessPoint = 0x08, + kFlag_Dropable = 0x04, + kFlag_Discontinuity = 0x02, + kFlag_EndOfStream = 0x01, + }; + + TRTPAudioPacket() + : TRTPPacket(kHeaderTypeAudio) + , mCodecType(kCodecInvalid) + , mRandomAccessPoint(false) + , mDropable(false) + , mDiscontinuity(false) + , mEndOfStream(false) + , mVolume(0) + , mAccessUnitData(NULL) + , mAccessUnitLen(0) + , mAuxData(NULL) + , mAuxDataLen(0) { } + + enum TRTPAudioCodecType { + kCodecInvalid = 0, + kCodecPCMBigEndian = 1, + kCodecPCMLittleEndian = 2, + kCodecMPEG1Audio = 3, + kCodecAACAudio = 4, + }; + + void setCodecType(TRTPAudioCodecType val); + void setRandomAccessPoint(bool val); + void setDropable(bool val); + void setDiscontinuity(bool val); + void setEndOfStream(bool val); + void setVolume(uint8_t val); + void setAccessUnitData(const void* data, size_t len); + void setAuxData(const void* data, size_t len); + + virtual bool pack(); + + protected: + virtual int TRTPHeaderLen() const; + + private: + TRTPAudioCodecType mCodecType; + bool mRandomAccessPoint; + bool mDropable; + bool mDiscontinuity; + bool mEndOfStream; + uint8_t mVolume; + + const void* mAccessUnitData; + size_t mAccessUnitLen; + const void* mAuxData; + size_t mAuxDataLen; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPAudioPacket); +}; + +class TRTPControlPacket : public TRTPPacket { + public: + TRTPControlPacket() + : TRTPPacket(kHeaderTypeControl) + , mCommandID(kCommandNop) {} + + enum TRTPCommandID { + kCommandNop = 1, + kCommandFlush = 2, + kCommandEOS = 3, + }; + + void setCommandID(TRTPCommandID val); + + virtual bool pack(); + + private: + TRTPCommandID mCommandID; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPControlPacket); +}; + +} // namespace android + +#endif // __AAH_TX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp new file mode 100644 index 0000000..974805b --- /dev/null +++ b/media/libaah_rtp/aah_tx_player.cpp @@ -0,0 +1,1177 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <netdb.h> +#include <netinet/ip.h> + +#include <common_time/cc_helper.h> +#include <media/IMediaPlayer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <utils/Timers.h> + +#include "aah_tx_packet.h" +#include "aah_tx_player.h" + +namespace android { + +static int64_t kLowWaterMarkUs = 2000000ll; // 2secs +static int64_t kHighWaterMarkUs = 10000000ll; // 10secs +static const size_t kLowWaterMarkBytes = 40000; +static const size_t kHighWaterMarkBytes = 200000; + +// When we start up, how much lead time should we put on the first access unit? +static const int64_t kAAHStartupLeadTimeUs = 300000LL; + +// How much time do we attempt to lead the clock by in steady state? +static const int64_t kAAHBufferTimeUs = 1000000LL; + +// how long do we keep data in our retransmit buffer after sending it. +const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs = + kAAHBufferTimeUs * 1100; + +sp<MediaPlayerBase> createAAH_TXPlayer() { + sp<MediaPlayerBase> ret = new AAH_TXPlayer(); + return ret; +} + +template <typename T> static T clamp(T val, T min, T max) { + if (val < min) { + return min; + } else if (val > max) { + return max; + } else { + return val; + } +} + +struct AAH_TXEvent : public TimedEventQueue::Event { + AAH_TXEvent(AAH_TXPlayer *player, + void (AAH_TXPlayer::*method)()) : mPlayer(player) + , mMethod(method) {} + + protected: + virtual ~AAH_TXEvent() {} + + virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) { + (mPlayer->*mMethod)(); + } + + private: + AAH_TXPlayer *mPlayer; + void (AAH_TXPlayer::*mMethod)(); + + AAH_TXEvent(const AAH_TXEvent &); + AAH_TXEvent& operator=(const AAH_TXEvent &); +}; + +AAH_TXPlayer::AAH_TXPlayer() + : mQueueStarted(false) + , mFlags(0) + , mExtractorFlags(0) { + DataSource::RegisterDefaultSniffers(); + + mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate); + mBufferingEventPending = false; + + mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio); + mPumpAudioEventPending = false; + + mAudioCodecData = NULL; + + reset_l(); +} + +AAH_TXPlayer::~AAH_TXPlayer() { + if (mQueueStarted) { + mQueue.stop(); + } + + reset_l(); +} + +void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) { + if (!keepBufferingGoing) { + mQueue.cancelEvent(mBufferingEvent->eventID()); + mBufferingEventPending = false; + + mQueue.cancelEvent(mPumpAudioEvent->eventID()); + mPumpAudioEventPending = false; + } +} + +status_t AAH_TXPlayer::initCheck() { + // Check for the presense of the common time service by attempting to query + // for CommonTime's frequency. If we get an error back, we cannot talk to + // the service at all and should abort now. + status_t res; + uint64_t freq; + res = mCCHelper.getCommonFreq(&freq); + if (OK != res) { + ALOGE("Failed to connect to common time service! (res %d)", res); + return res; + } + + return OK; +} + +status_t AAH_TXPlayer::setDataSource( + const char *url, + const KeyedVector<String8, String8> *headers) { + Mutex::Autolock autoLock(mLock); + return setDataSource_l(url, headers); +} + +status_t AAH_TXPlayer::setDataSource_l( + const char *url, + const KeyedVector<String8, String8> *headers) { + reset_l(); + + mUri.setTo(url); + + if (headers) { + mUriHeaders = *headers; + + ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log")); + if (index >= 0) { + // Browser is in "incognito" mode, suppress logging URLs. + + // This isn't something that should be passed to the server. + mUriHeaders.removeItemsAt(index); + + mFlags |= INCOGNITO; + } + } + + // The URL may optionally contain a "#" character followed by a Skyjam + // cookie. Ideally the cookie header should just be passed in the headers + // argument, but the Java API for supplying headers is apparently not yet + // exposed in the SDK used by application developers. + const char kSkyjamCookieDelimiter = '#'; + char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter); + if (skyjamCookie) { + skyjamCookie++; + mUriHeaders.add(String8("Cookie"), String8(skyjamCookie)); + mUri = String8(mUri.string(), skyjamCookie - mUri.string()); + } + + return OK; +} + +status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + Mutex::Autolock autoLock(mLock); + + reset_l(); + + sp<DataSource> dataSource = new FileSource(dup(fd), offset, length); + + status_t err = dataSource->initCheck(); + + if (err != OK) { + return err; + } + + mFileSource = dataSource; + + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + + if (extractor == NULL) { + return UNKNOWN_ERROR; + } + + return setDataSource_l(extractor); +} + +status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) { + return OK; +} + +status_t AAH_TXPlayer::setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) { + return OK; +} + +status_t AAH_TXPlayer::prepare() { + return INVALID_OPERATION; +} + +status_t AAH_TXPlayer::prepareAsync() { + Mutex::Autolock autoLock(mLock); + + return prepareAsync_l(); +} + +status_t AAH_TXPlayer::prepareAsync_l() { + if (mFlags & PREPARING) { + return UNKNOWN_ERROR; // async prepare already pending + } + + mAAH_Sender = AAH_TXSender::GetInstance(); + if (mAAH_Sender == NULL) { + return NO_MEMORY; + } + + if (!mQueueStarted) { + mQueue.start(); + mQueueStarted = true; + } + + mFlags |= PREPARING; + mAsyncPrepareEvent = new AAH_TXEvent( + this, &AAH_TXPlayer::onPrepareAsyncEvent); + + mQueue.postEvent(mAsyncPrepareEvent); + + return OK; +} + +status_t AAH_TXPlayer::finishSetDataSource_l() { + sp<DataSource> dataSource; + + if (!strncasecmp("http://", mUri.string(), 7) || + !strncasecmp("https://", mUri.string(), 8)) { + + mConnectingDataSource = HTTPBase::Create( + (mFlags & INCOGNITO) + ? HTTPBase::kFlagIncognito + : 0); + + mLock.unlock(); + status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders); + mLock.lock(); + + if (err != OK) { + mConnectingDataSource.clear(); + + ALOGI("mConnectingDataSource->connect() returned %d", err); + return err; + } + + mCachedSource = new NuCachedSource2(mConnectingDataSource); + mConnectingDataSource.clear(); + + dataSource = mCachedSource; + + // We're going to prefill the cache before trying to instantiate + // the extractor below, as the latter is an operation that otherwise + // could block on the datasource for a significant amount of time. + // During that time we'd be unable to abort the preparation phase + // without this prefill. + + mLock.unlock(); + + for (;;) { + status_t finalStatus; + size_t cachedDataRemaining = + mCachedSource->approxDataRemaining(&finalStatus); + + if (finalStatus != OK || + cachedDataRemaining >= kHighWaterMarkBytes || + (mFlags & PREPARE_CANCELLED)) { + break; + } + + usleep(200000); + } + + mLock.lock(); + + if (mFlags & PREPARE_CANCELLED) { + ALOGI("Prepare cancelled while waiting for initial cache fill."); + return UNKNOWN_ERROR; + } + } else { + dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders); + } + + if (dataSource == NULL) { + return UNKNOWN_ERROR; + } + + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + + if (extractor == NULL) { + return UNKNOWN_ERROR; + } + + return setDataSource_l(extractor); +} + +status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) { + // Attempt to approximate overall stream bitrate by summing all + // tracks' individual bitrates, if not all of them advertise bitrate, + // we have to fail. + + int64_t totalBitRate = 0; + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + int32_t bitrate; + if (!meta->findInt32(kKeyBitRate, &bitrate)) { + totalBitRate = -1; + break; + } + + totalBitRate += bitrate; + } + + mBitrate = totalBitRate; + + ALOGV("mBitrate = %lld bits/sec", mBitrate); + + bool haveAudio = false; + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp(mime, "audio/", 6)) { + mAudioSource = extractor->getTrack(i); + CHECK(mAudioSource != NULL); + haveAudio = true; + break; + } + } + + if (!haveAudio) { + return UNKNOWN_ERROR; + } + + mExtractorFlags = extractor->flags(); + + return OK; +} + +void AAH_TXPlayer::abortPrepare(status_t err) { + CHECK(err != OK); + + notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err); + + mPrepareResult = err; + mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED); + mPreparedCondition.broadcast(); +} + +void AAH_TXPlayer::onPrepareAsyncEvent() { + Mutex::Autolock autoLock(mLock); + + if (mFlags & PREPARE_CANCELLED) { + ALOGI("prepare was cancelled before doing anything"); + abortPrepare(UNKNOWN_ERROR); + return; + } + + if (mUri.size() > 0) { + status_t err = finishSetDataSource_l(); + + if (err != OK) { + abortPrepare(err); + return; + } + } + + mAudioFormat = mAudioSource->getFormat(); + if (!mAudioFormat->findInt64(kKeyDuration, &mDurationUs)) + mDurationUs = 1; + + const char* mime_type = NULL; + if (!mAudioFormat->findCString(kKeyMIMEType, &mime_type)) { + ALOGE("Failed to find audio substream MIME type during prepare."); + abortPrepare(BAD_VALUE); + return; + } + + if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_MPEG)) { + mAudioCodec = TRTPAudioPacket::kCodecMPEG1Audio; + } else + if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_AAC)) { + mAudioCodec = TRTPAudioPacket::kCodecAACAudio; + + uint32_t type; + int32_t sample_rate; + int32_t channel_count; + const void* esds_data; + size_t esds_len; + + if (!mAudioFormat->findInt32(kKeySampleRate, &sample_rate)) { + ALOGE("Failed to find sample rate for AAC substream."); + abortPrepare(BAD_VALUE); + return; + } + + if (!mAudioFormat->findInt32(kKeyChannelCount, &channel_count)) { + ALOGE("Failed to find channel count for AAC substream."); + abortPrepare(BAD_VALUE); + return; + } + + if (!mAudioFormat->findData(kKeyESDS, &type, &esds_data, &esds_len)) { + ALOGE("Failed to find codec init data for AAC substream."); + abortPrepare(BAD_VALUE); + return; + } + + CHECK(NULL == mAudioCodecData); + mAudioCodecDataSize = esds_len + + sizeof(sample_rate) + + sizeof(channel_count); + mAudioCodecData = new uint8_t[mAudioCodecDataSize]; + if (NULL == mAudioCodecData) { + ALOGE("Failed to allocate %u bytes for AAC substream codec aux" + " data.", mAudioCodecDataSize); + mAudioCodecDataSize = 0; + abortPrepare(BAD_VALUE); + return; + } + + uint8_t* tmp = mAudioCodecData; + tmp[0] = static_cast<uint8_t>((sample_rate >> 24) & 0xFF); + tmp[1] = static_cast<uint8_t>((sample_rate >> 16) & 0xFF); + tmp[2] = static_cast<uint8_t>((sample_rate >> 8) & 0xFF); + tmp[3] = static_cast<uint8_t>((sample_rate ) & 0xFF); + tmp[4] = static_cast<uint8_t>((channel_count >> 24) & 0xFF); + tmp[5] = static_cast<uint8_t>((channel_count >> 16) & 0xFF); + tmp[6] = static_cast<uint8_t>((channel_count >> 8) & 0xFF); + tmp[7] = static_cast<uint8_t>((channel_count ) & 0xFF); + + memcpy(tmp + 8, esds_data, esds_len); + } else { + ALOGE("Unsupported MIME type \"%s\" in audio substream", mime_type); + abortPrepare(BAD_VALUE); + return; + } + + status_t err = mAudioSource->start(); + if (err != OK) { + ALOGI("failed to start audio source, err=%d", err); + abortPrepare(err); + return; + } + + mFlags |= PREPARING_CONNECTED; + + if (mCachedSource != NULL) { + postBufferingEvent_l(); + } else { + finishAsyncPrepare_l(); + } +} + +void AAH_TXPlayer::finishAsyncPrepare_l() { + notifyListener_l(MEDIA_PREPARED); + + mPrepareResult = OK; + mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED); + mFlags |= PREPARED; + mPreparedCondition.broadcast(); +} + +status_t AAH_TXPlayer::start() { + Mutex::Autolock autoLock(mLock); + + mFlags &= ~CACHE_UNDERRUN; + + return play_l(); +} + +status_t AAH_TXPlayer::play_l() { + if (mFlags & PLAYING) { + return OK; + } + + if (!(mFlags & PREPARED)) { + return INVALID_OPERATION; + } + + { + Mutex::Autolock lock(mEndpointLock); + if (!mEndpointValid) { + return INVALID_OPERATION; + } + if (!mEndpointRegistered) { + mProgramID = mAAH_Sender->registerEndpoint(mEndpoint); + mEndpointRegistered = true; + } + } + + mFlags |= PLAYING; + + updateClockTransform_l(false); + + postPumpAudioEvent_l(-1); + + return OK; +} + +status_t AAH_TXPlayer::stop() { + status_t ret = pause(); + sendEOS_l(); + return ret; +} + +status_t AAH_TXPlayer::pause() { + Mutex::Autolock autoLock(mLock); + + mFlags &= ~CACHE_UNDERRUN; + + return pause_l(); +} + +status_t AAH_TXPlayer::pause_l(bool doClockUpdate) { + if (!(mFlags & PLAYING)) { + return OK; + } + + cancelPlayerEvents(true /* keepBufferingGoing */); + + mFlags &= ~PLAYING; + + if (doClockUpdate) { + updateClockTransform_l(true); + } + + return OK; +} + +void AAH_TXPlayer::updateClockTransform_l(bool pause) { + // record the new pause status so that onPumpAudio knows what rate to apply + // when it initializes the transform + mPlayRateIsPaused = pause; + + // if we haven't yet established a valid clock transform, then we can't + // do anything here + if (!mCurrentClockTransformValid) { + return; + } + + // sample the current common time + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + ALOGE("updateClockTransform_l get common time failed"); + mCurrentClockTransformValid = false; + return; + } + + // convert the current common time to media time using the old + // transform + int64_t mediaTimeNow; + if (!mCurrentClockTransform.doReverseTransform( + commonTimeNow, &mediaTimeNow)) { + ALOGE("updateClockTransform_l reverse transform failed"); + mCurrentClockTransformValid = false; + return; + } + + // calculate a new transform that preserves the old transform's + // result for the current time + mCurrentClockTransform.a_zero = mediaTimeNow; + mCurrentClockTransform.b_zero = commonTimeNow; + mCurrentClockTransform.a_to_b_numer = 1; + mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1; + + // send a packet announcing the new transform + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setClockTransform(mCurrentClockTransform); + packet->setCommandID(TRTPControlPacket::kCommandNop); + queuePacketToSender_l(packet); +} + +void AAH_TXPlayer::sendEOS_l() { + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandEOS); + queuePacketToSender_l(packet); +} + +bool AAH_TXPlayer::isPlaying() { + return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN); +} + +status_t AAH_TXPlayer::seekTo(int msec) { + if (mExtractorFlags & MediaExtractor::CAN_SEEK) { + Mutex::Autolock autoLock(mLock); + return seekTo_l(static_cast<int64_t>(msec) * 1000); + } + + notifyListener_l(MEDIA_SEEK_COMPLETE); + return OK; +} + +status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) { + mIsSeeking = true; + mSeekTimeUs = timeUs; + + mCurrentClockTransformValid = false; + mLastQueuedMediaTimePTSValid = false; + + // send a flush command packet + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandFlush); + queuePacketToSender_l(packet); + + return OK; +} + +status_t AAH_TXPlayer::getCurrentPosition(int *msec) { + if (!msec) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mLock); + + int position; + + if (mIsSeeking) { + position = mSeekTimeUs / 1000; + } else if (mCurrentClockTransformValid) { + // sample the current common time + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + ALOGE("getCurrentPosition get common time failed"); + return INVALID_OPERATION; + } + + int64_t mediaTimeNow; + if (!mCurrentClockTransform.doReverseTransform(commonTimeNow, + &mediaTimeNow)) { + ALOGE("getCurrentPosition reverse transform failed"); + return INVALID_OPERATION; + } + + position = static_cast<int>(mediaTimeNow / 1000); + } else { + position = 0; + } + + int duration; + if (getDuration_l(&duration) == OK) { + *msec = clamp(position, 0, duration); + } else { + *msec = (position >= 0) ? position : 0; + } + + return OK; +} + +status_t AAH_TXPlayer::getDuration(int* msec) { + if (!msec) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mLock); + + return getDuration_l(msec); +} + +status_t AAH_TXPlayer::getDuration_l(int* msec) { + if (mDurationUs < 0) { + return UNKNOWN_ERROR; + } + + *msec = (mDurationUs + 500) / 1000; + + return OK; +} + +status_t AAH_TXPlayer::reset() { + Mutex::Autolock autoLock(mLock); + reset_l(); + return OK; +} + +void AAH_TXPlayer::reset_l() { + if (mFlags & PREPARING) { + mFlags |= PREPARE_CANCELLED; + if (mConnectingDataSource != NULL) { + ALOGI("interrupting the connection process"); + mConnectingDataSource->disconnect(); + } + + if (mFlags & PREPARING_CONNECTED) { + // We are basically done preparing, we're just buffering + // enough data to start playback, we can safely interrupt that. + finishAsyncPrepare_l(); + } + } + + while (mFlags & PREPARING) { + mPreparedCondition.wait(mLock); + } + + cancelPlayerEvents(); + + sendEOS_l(); + + mCachedSource.clear(); + + if (mAudioSource != NULL) { + mAudioSource->stop(); + } + mAudioSource.clear(); + mAudioCodec = TRTPAudioPacket::kCodecInvalid; + mAudioFormat = NULL; + delete[] mAudioCodecData; + mAudioCodecData = NULL; + mAudioCodecDataSize = 0; + + mFlags = 0; + mExtractorFlags = 0; + + mDurationUs = -1; + mIsSeeking = false; + mSeekTimeUs = 0; + + mUri.setTo(""); + mUriHeaders.clear(); + + mFileSource.clear(); + + mBitrate = -1; + + { + Mutex::Autolock lock(mEndpointLock); + if (mAAH_Sender != NULL && mEndpointRegistered) { + mAAH_Sender->unregisterEndpoint(mEndpoint); + } + mEndpointRegistered = false; + mEndpointValid = false; + } + + mProgramID = 0; + + mAAH_Sender.clear(); + mLastQueuedMediaTimePTSValid = false; + mCurrentClockTransformValid = false; + mPlayRateIsPaused = false; + + mTRTPVolume = 255; +} + +status_t AAH_TXPlayer::setLooping(int loop) { + return OK; +} + +player_type AAH_TXPlayer::playerType() { + return AAH_TX_PLAYER; +} + +status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) { + return INVALID_OPERATION; +} + +status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids, + Parcel* records) { + using media::Metadata; + + Metadata metadata(records); + + metadata.appendBool(Metadata::kPauseAvailable, true); + metadata.appendBool(Metadata::kSeekBackwardAvailable, false); + metadata.appendBool(Metadata::kSeekForwardAvailable, false); + metadata.appendBool(Metadata::kSeekAvailable, false); + + return OK; +} + +status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) { + if (leftVolume != rightVolume) { + ALOGE("%s does not support per channel volume: %f, %f", + __PRETTY_FUNCTION__, leftVolume, rightVolume); + } + + float volume = clamp(leftVolume, 0.0f, 1.0f); + + Mutex::Autolock lock(mLock); + mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5); + + return OK; +} + +status_t AAH_TXPlayer::setAudioStreamType(audio_stream_type_t streamType) { + return OK; +} + +status_t AAH_TXPlayer::setRetransmitEndpoint( + const struct sockaddr_in* endpoint) { + Mutex::Autolock lock(mLock); + + if (NULL == endpoint) + return BAD_VALUE; + + // Once the endpoint has been registered, it may not be changed. + if (mEndpointRegistered) + return INVALID_OPERATION; + + mEndpoint.addr = endpoint->sin_addr.s_addr; + mEndpoint.port = endpoint->sin_port; + mEndpointValid = true; + + return OK; +} + +void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) { + sendEvent(msg, ext1, ext2); +} + +bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) { + off64_t size; + if (mDurationUs >= 0 && + mCachedSource != NULL && + mCachedSource->getSize(&size) == OK) { + *bitrate = size * 8000000ll / mDurationUs; // in bits/sec + return true; + } + + if (mBitrate >= 0) { + *bitrate = mBitrate; + return true; + } + + *bitrate = 0; + + return false; +} + +// Returns true iff cached duration is available/applicable. +bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) { + int64_t bitrate; + + if (mCachedSource != NULL && getBitrate_l(&bitrate)) { + status_t finalStatus; + size_t cachedDataRemaining = mCachedSource->approxDataRemaining( + &finalStatus); + *durationUs = cachedDataRemaining * 8000000ll / bitrate; + *eos = (finalStatus != OK); + return true; + } + + return false; +} + +void AAH_TXPlayer::ensureCacheIsFetching_l() { + if (mCachedSource != NULL) { + mCachedSource->resumeFetchingIfNecessary(); + } +} + +void AAH_TXPlayer::postBufferingEvent_l() { + if (mBufferingEventPending) { + return; + } + mBufferingEventPending = true; + mQueue.postEventWithDelay(mBufferingEvent, 1000000ll); +} + +void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) { + if (mPumpAudioEventPending) { + return; + } + mPumpAudioEventPending = true; + mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs); +} + +void AAH_TXPlayer::onBufferingUpdate() { + Mutex::Autolock autoLock(mLock); + if (!mBufferingEventPending) { + return; + } + mBufferingEventPending = false; + + if (mCachedSource != NULL) { + status_t finalStatus; + size_t cachedDataRemaining = mCachedSource->approxDataRemaining( + &finalStatus); + bool eos = (finalStatus != OK); + + if (eos) { + if (finalStatus == ERROR_END_OF_STREAM) { + notifyListener_l(MEDIA_BUFFERING_UPDATE, 100); + } + if (mFlags & PREPARING) { + ALOGV("cache has reached EOS, prepare is done."); + finishAsyncPrepare_l(); + } + } else { + int64_t bitrate; + if (getBitrate_l(&bitrate)) { + size_t cachedSize = mCachedSource->cachedSize(); + int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate; + + int percentage = (100.0 * (double) cachedDurationUs) + / mDurationUs; + if (percentage > 100) { + percentage = 100; + } + + notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage); + } else { + // We don't know the bitrate of the stream, use absolute size + // limits to maintain the cache. + + if ((mFlags & PLAYING) && + !eos && + (cachedDataRemaining < kLowWaterMarkBytes)) { + ALOGI("cache is running low (< %d) , pausing.", + kLowWaterMarkBytes); + mFlags |= CACHE_UNDERRUN; + pause_l(); + ensureCacheIsFetching_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START); + } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) { + if (mFlags & CACHE_UNDERRUN) { + ALOGI("cache has filled up (> %d), resuming.", + kHighWaterMarkBytes); + mFlags &= ~CACHE_UNDERRUN; + play_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END); + } else if (mFlags & PREPARING) { + ALOGV("cache has filled up (> %d), prepare is done", + kHighWaterMarkBytes); + finishAsyncPrepare_l(); + } + } + } + } + } + + int64_t cachedDurationUs; + bool eos; + if (getCachedDuration_l(&cachedDurationUs, &eos)) { + ALOGV("cachedDurationUs = %.2f secs, eos=%d", + cachedDurationUs / 1E6, eos); + + if ((mFlags & PLAYING) && + !eos && + (cachedDurationUs < kLowWaterMarkUs)) { + ALOGI("cache is running low (%.2f secs) , pausing.", + cachedDurationUs / 1E6); + mFlags |= CACHE_UNDERRUN; + pause_l(); + ensureCacheIsFetching_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START); + } else if (eos || cachedDurationUs > kHighWaterMarkUs) { + if (mFlags & CACHE_UNDERRUN) { + ALOGI("cache has filled up (%.2f secs), resuming.", + cachedDurationUs / 1E6); + mFlags &= ~CACHE_UNDERRUN; + play_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END); + } else if (mFlags & PREPARING) { + ALOGV("cache has filled up (%.2f secs), prepare is done", + cachedDurationUs / 1E6); + finishAsyncPrepare_l(); + } + } + } + + postBufferingEvent_l(); +} + +void AAH_TXPlayer::onPumpAudio() { + while (true) { + Mutex::Autolock autoLock(mLock); + // If this flag is clear, its because someone has externally canceled + // this pump operation (probably because we a resetting/shutting down). + // Get out immediately, do not reschedule ourselves. + if (!mPumpAudioEventPending) { + return; + } + + // Start by checking if there is still work to be doing. If we have + // never queued a payload (so we don't know what the last queued PTS is) + // or we have never established a MediaTime->CommonTime transformation, + // then we have work to do (one time through this loop should establish + // both). Otherwise, we want to keep a fixed amt of presentation time + // worth of data buffered. If we cannot get common time (service is + // unavailable, or common time is undefined)) then we don't have a lot + // of good options here. For now, signal an error up to the app level + // and shut down the transmission pump. + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + // Failed to get common time; either the service is down or common + // time is not synced. Raise an error and shutdown the player. + ALOGE("*** Cannot pump audio, unable to fetch common time." + " Shutting down."); + notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR); + mPumpAudioEventPending = false; + break; + } + + if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) { + int64_t mediaTimeNow; + bool conversionResult = mCurrentClockTransform.doReverseTransform( + commonTimeNow, + &mediaTimeNow); + CHECK(conversionResult); + + if ((mediaTimeNow + + kAAHBufferTimeUs - + mLastQueuedMediaTimePTS) <= 0) { + break; + } + } + + MediaSource::ReadOptions options; + if (mIsSeeking) { + options.setSeekTo(mSeekTimeUs); + } + + MediaBuffer* mediaBuffer; + status_t err = mAudioSource->read(&mediaBuffer, &options); + if (err != NO_ERROR) { + if (err == ERROR_END_OF_STREAM) { + ALOGI("*** %s reached end of stream", __PRETTY_FUNCTION__); + notifyListener_l(MEDIA_BUFFERING_UPDATE, 100); + notifyListener_l(MEDIA_PLAYBACK_COMPLETE); + pause_l(false); + sendEOS_l(); + } else { + ALOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err); + } + return; + } + + if (mIsSeeking) { + mIsSeeking = false; + notifyListener_l(MEDIA_SEEK_COMPLETE); + } + + uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) + + mediaBuffer->range_offset()); + ALOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]" + " offset=%d length=%d", __PRETTY_FUNCTION__, + data[0], data[1], data[2], data[3], + mediaBuffer->range_offset(), mediaBuffer->range_length()); + + int64_t mediaTimeUs; + CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs)); + ALOGV("*** timeUs=%lld", mediaTimeUs); + + if (!mCurrentClockTransformValid) { + if (OK == mCCHelper.getCommonTime(&commonTimeNow)) { + mCurrentClockTransform.a_zero = mediaTimeUs; + mCurrentClockTransform.b_zero = commonTimeNow + + kAAHStartupLeadTimeUs; + mCurrentClockTransform.a_to_b_numer = 1; + mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1; + mCurrentClockTransformValid = true; + } else { + // Failed to get common time; either the service is down or + // common time is not synced. Raise an error and shutdown the + // player. + ALOGE("*** Cannot begin transmission, unable to fetch common" + " time. Dropping sample with pts=%lld", mediaTimeUs); + notifyListener_l(MEDIA_ERROR, + MEDIA_ERROR_UNKNOWN, + UNKNOWN_ERROR); + mPumpAudioEventPending = false; + break; + } + } + + ALOGV("*** transmitting packet with pts=%lld", mediaTimeUs); + + sp<TRTPAudioPacket> packet = new TRTPAudioPacket(); + packet->setPTS(mediaTimeUs); + packet->setSubstreamID(1); + + packet->setCodecType(mAudioCodec); + packet->setVolume(mTRTPVolume); + // TODO : introduce a throttle for this so we can control the + // frequency with which transforms get sent. + packet->setClockTransform(mCurrentClockTransform); + packet->setAccessUnitData(data, mediaBuffer->range_length()); + + // TODO : while its pretty much universally true that audio ES payloads + // are all RAPs across all codecs, it might be a good idea to throttle + // the frequency with which we send codec out of band data to the RXers. + // If/when we do, we need to flag only those payloads which have + // required out of band data attached to them as RAPs. + packet->setRandomAccessPoint(true); + + if (mAudioCodecData && mAudioCodecDataSize) { + packet->setAuxData(mAudioCodecData, mAudioCodecDataSize); + } + + queuePacketToSender_l(packet); + mediaBuffer->release(); + + mLastQueuedMediaTimePTSValid = true; + mLastQueuedMediaTimePTS = mediaTimeUs; + } + + { // Explicit scope for the autolock pattern. + Mutex::Autolock autoLock(mLock); + + // If someone externally has cleared this flag, its because we should be + // shutting down. Do not reschedule ourselves. + if (!mPumpAudioEventPending) { + return; + } + + // Looks like no one canceled us explicitly. Clear our flag and post a + // new event to ourselves. + mPumpAudioEventPending = false; + postPumpAudioEvent_l(10000); + } +} + +void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) { + if (mAAH_Sender == NULL) { + return; + } + + sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket, + mAAH_Sender->handlerID()); + + { + Mutex::Autolock lock(mEndpointLock); + if (!mEndpointValid) { + return; + } + + message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr); + message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port); + } + + packet->setProgramID(mProgramID); + packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs); + packet->pack(); + + message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet); + + message->post(); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h new file mode 100644 index 0000000..2e4b1f7 --- /dev/null +++ b/media/libaah_rtp/aah_tx_player.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __AAH_TX_PLAYER_H__ +#define __AAH_TX_PLAYER_H__ + +#include <common_time/cc_helper.h> +#include <libstagefright/include/HTTPBase.h> +#include <libstagefright/include/NuCachedSource2.h> +#include <libstagefright/include/TimedEventQueue.h> +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <utils/LinearTransform.h> +#include <utils/String8.h> +#include <utils/threads.h> + +#include "aah_tx_sender.h" + +namespace android { + +class AAH_TXPlayer : public MediaPlayerHWInterface { + public: + AAH_TXPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url, + const KeyedVector<String8, String8>* + headers); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<Surface>& surface); + virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>& + surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + virtual status_t invoke(const Parcel& request, Parcel *reply); + virtual status_t getMetadata(const media::Metadata::Filter& ids, + Parcel* records); + virtual status_t setVolume(float leftVolume, float rightVolume); + virtual status_t setAudioStreamType(audio_stream_type_t streamType); + virtual status_t setRetransmitEndpoint( + const struct sockaddr_in* endpoint); + + static const int64_t kAAHRetryKeepAroundTimeNs; + + protected: + virtual ~AAH_TXPlayer(); + + private: + friend struct AwesomeEvent; + + enum { + PLAYING = 1, + PREPARING = 8, + PREPARED = 16, + PREPARE_CANCELLED = 64, + CACHE_UNDERRUN = 128, + + // We are basically done preparing but are currently buffering + // sufficient data to begin playback and finish the preparation + // phase for good. + PREPARING_CONNECTED = 2048, + + INCOGNITO = 32768, + }; + + status_t setDataSource_l(const char *url, + const KeyedVector<String8, String8> *headers); + status_t setDataSource_l(const sp<MediaExtractor>& extractor); + status_t finishSetDataSource_l(); + status_t prepareAsync_l(); + void onPrepareAsyncEvent(); + void finishAsyncPrepare_l(); + void abortPrepare(status_t err); + status_t play_l(); + status_t pause_l(bool doClockUpdate = true); + status_t seekTo_l(int64_t timeUs); + void updateClockTransform_l(bool pause); + void sendEOS_l(); + void cancelPlayerEvents(bool keepBufferingGoing = false); + void reset_l(); + void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0); + bool getBitrate_l(int64_t* bitrate); + status_t getDuration_l(int* msec); + bool getCachedDuration_l(int64_t* durationUs, bool* eos); + void ensureCacheIsFetching_l(); + void postBufferingEvent_l(); + void postPumpAudioEvent_l(int64_t delayUs); + void onBufferingUpdate(); + void onPumpAudio(); + void queuePacketToSender_l(const sp<TRTPPacket>& packet); + + Mutex mLock; + + TimedEventQueue mQueue; + bool mQueueStarted; + + sp<TimedEventQueue::Event> mBufferingEvent; + bool mBufferingEventPending; + + uint32_t mFlags; + uint32_t mExtractorFlags; + + String8 mUri; + KeyedVector<String8, String8> mUriHeaders; + + sp<DataSource> mFileSource; + + sp<TimedEventQueue::Event> mAsyncPrepareEvent; + Condition mPreparedCondition; + status_t mPrepareResult; + + bool mIsSeeking; + int64_t mSeekTimeUs; + + sp<TimedEventQueue::Event> mPumpAudioEvent; + bool mPumpAudioEventPending; + + sp<HTTPBase> mConnectingDataSource; + sp<NuCachedSource2> mCachedSource; + + sp<MediaSource> mAudioSource; + TRTPAudioPacket::TRTPAudioCodecType mAudioCodec; + sp<MetaData> mAudioFormat; + uint8_t* mAudioCodecData; + size_t mAudioCodecDataSize; + + int64_t mDurationUs; + int64_t mBitrate; + + sp<AAH_TXSender> mAAH_Sender; + LinearTransform mCurrentClockTransform; + bool mCurrentClockTransformValid; + int64_t mLastQueuedMediaTimePTS; + bool mLastQueuedMediaTimePTSValid; + bool mPlayRateIsPaused; + CCHelper mCCHelper; + + Mutex mEndpointLock; + AAH_TXSender::Endpoint mEndpoint; + bool mEndpointValid; + bool mEndpointRegistered; + uint16_t mProgramID; + uint8_t mTRTPVolume; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer); +}; + +} // namespace android + +#endif // __AAH_TX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp new file mode 100644 index 0000000..08e32d2 --- /dev/null +++ b/media/libaah_rtp/aah_tx_sender.cpp @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <media/stagefright/foundation/ADebug.h> + +#include <netinet/in.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <media/stagefright/foundation/AMessage.h> +#include <utils/misc.h> + +#include "aah_tx_player.h" +#include "aah_tx_sender.h" + +namespace android { + +const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr"; +const char* AAH_TXSender::kSendPacketPort = "port"; +const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp"; + +const int AAH_TXSender::kRetryTrimIntervalUs = 100000; +const int AAH_TXSender::kHeartbeatIntervalUs = 1000000; +const int AAH_TXSender::kRetryBufferCapacity = 100; +const nsecs_t AAH_TXSender::kHeartbeatTimeout = 600ull * 1000000000ull; + +Mutex AAH_TXSender::sLock; +wp<AAH_TXSender> AAH_TXSender::sInstance; +uint32_t AAH_TXSender::sNextEpoch; +bool AAH_TXSender::sNextEpochValid = false; + +AAH_TXSender::AAH_TXSender() : mSocket(-1) { + mLastSentPacketTime = systemTime(); +} + +sp<AAH_TXSender> AAH_TXSender::GetInstance() { + Mutex::Autolock autoLock(sLock); + + sp<AAH_TXSender> sender = sInstance.promote(); + + if (sender == NULL) { + sender = new AAH_TXSender(); + if (sender == NULL) { + return NULL; + } + + sender->mLooper = new ALooper(); + if (sender->mLooper == NULL) { + return NULL; + } + + sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get()); + if (sender->mReflector == NULL) { + return NULL; + } + + sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sender->mSocket == -1) { + ALOGW("%s unable to create socket", __PRETTY_FUNCTION__); + return NULL; + } + + struct sockaddr_in bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + if (bind(sender->mSocket, + reinterpret_cast<const sockaddr*>(&bind_addr), + sizeof(bind_addr)) < 0) { + ALOGW("%s unable to bind socket (errno %d)", + __PRETTY_FUNCTION__, errno); + return NULL; + } + + sender->mRetryReceiver = new RetryReceiver(sender.get()); + if (sender->mRetryReceiver == NULL) { + return NULL; + } + + sender->mLooper->setName("AAH_TXSender"); + sender->mLooper->registerHandler(sender->mReflector); + sender->mLooper->start(false, false, PRIORITY_AUDIO); + + if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO) + != OK) { + ALOGW("%s unable to start retry thread", __PRETTY_FUNCTION__); + return NULL; + } + + sInstance = sender; + } + + return sender; +} + +AAH_TXSender::~AAH_TXSender() { + mLooper->stop(); + mLooper->unregisterHandler(mReflector->id()); + + if (mRetryReceiver != NULL) { + mRetryReceiver->requestExit(); + mRetryReceiver->mWakeupEvent.setEvent(); + if (mRetryReceiver->requestExitAndWait() != OK) { + ALOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__); + } + mRetryReceiver->mSender = NULL; + mRetryReceiver.clear(); + } + + if (mSocket != -1) { + close(mSocket); + } +} + +// Return the next epoch number usable for a newly instantiated endpoint. +uint32_t AAH_TXSender::getNextEpoch() { + Mutex::Autolock autoLock(sLock); + + if (sNextEpochValid) { + sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask; + } else { + sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask; + sNextEpochValid = true; + } + + return sNextEpoch; +} + +// Notify the sender that a player has started sending to this endpoint. +// Returns a program ID for use by the calling player. +uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) { + Mutex::Autolock lock(mEndpointLock); + + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (eps) { + eps->playerRefCount++; + } else { + eps = new EndpointState(getNextEpoch()); + mEndpointMap.add(endpoint, eps); + } + + // if this is the first registered endpoint, then send a message to start + // trimming retry buffers and a message to start sending heartbeats. + if (mEndpointMap.size() == 1) { + sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers, + handlerID()); + trimMessage->post(kRetryTrimIntervalUs); + + sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats, + handlerID()); + heartbeatMessage->post(kHeartbeatIntervalUs); + } + + eps->nextProgramID++; + return eps->nextProgramID; +} + +// Notify the sender that a player has ceased sending to this endpoint. +// An endpoint's state can not be deleted until all of the endpoint's +// registered players have called unregisterEndpoint. +void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) { + Mutex::Autolock lock(mEndpointLock); + + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (eps) { + eps->playerRefCount--; + CHECK(eps->playerRefCount >= 0); + } +} + +void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) { + switch (msg->what()) { + case kWhatSendPacket: + onSendPacket(msg); + break; + + case kWhatTrimRetryBuffers: + trimRetryBuffers(); + break; + + case kWhatSendHeartbeats: + sendHeartbeats(); + break; + + default: + TRESPASS(); + break; + } +} + +void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) { + sp<RefBase> obj; + CHECK(msg->findObject(kSendPacketTRTPPacket, &obj)); + sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get()); + + uint32_t ipAddr; + CHECK(msg->findInt32(kSendPacketIPAddr, + reinterpret_cast<int32_t*>(&ipAddr))); + + int32_t port32; + CHECK(msg->findInt32(kSendPacketPort, &port32)); + uint16_t port = port32; + + Mutex::Autolock lock(mEndpointLock); + doSendPacket_l(packet, Endpoint(ipAddr, port)); + mLastSentPacketTime = systemTime(); +} + +void AAH_TXSender::doSendPacket_l(const sp<TRTPPacket>& packet, + const Endpoint& endpoint) { + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (!eps) { + // the endpoint state has disappeared, so the player that sent this + // packet must be dead. + return; + } + + // assign the packet's sequence number + packet->setEpoch(eps->epoch); + packet->setSeqNumber(eps->trtpSeqNumber++); + + // add the packet to the retry buffer + RetryBuffer& retry = eps->retry; + retry.push_back(packet); + + // send the packet + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = endpoint.addr; + addr.sin_port = endpoint.port; + + ssize_t result = sendto(mSocket, + packet->getPacket(), + packet->getPacketLen(), + 0, + (const struct sockaddr *) &addr, + sizeof(addr)); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } +} + +void AAH_TXSender::trimRetryBuffers() { + Mutex::Autolock lock(mEndpointLock); + + nsecs_t localTimeNow = systemTime(); + + Vector<Endpoint> endpointsToRemove; + + for (size_t i = 0; i < mEndpointMap.size(); i++) { + EndpointState* eps = mEndpointMap.editValueAt(i); + RetryBuffer& retry = eps->retry; + + while (!retry.isEmpty()) { + if (retry[0]->getExpireTime() < localTimeNow) { + retry.pop_front(); + } else { + break; + } + } + + if (retry.isEmpty() && eps->playerRefCount == 0) { + endpointsToRemove.add(mEndpointMap.keyAt(i)); + } + } + + // remove the state for any endpoints that are no longer in use + for (size_t i = 0; i < endpointsToRemove.size(); i++) { + Endpoint& e = endpointsToRemove.editItemAt(i); + ALOGD("*** %s removing endpoint addr=%08x", + __PRETTY_FUNCTION__, e.addr); + size_t index = mEndpointMap.indexOfKey(e); + delete mEndpointMap.valueAt(index); + mEndpointMap.removeItemsAt(index); + } + + // schedule the next trim + if (mEndpointMap.size()) { + sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers, + handlerID()); + trimMessage->post(kRetryTrimIntervalUs); + } +} + +void AAH_TXSender::sendHeartbeats() { + Mutex::Autolock lock(mEndpointLock); + + if (shouldSendHeartbeats_l()) { + for (size_t i = 0; i < mEndpointMap.size(); i++) { + EndpointState* eps = mEndpointMap.editValueAt(i); + const Endpoint& ep = mEndpointMap.keyAt(i); + + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandNop); + + packet->setExpireTime(systemTime() + + AAH_TXPlayer::kAAHRetryKeepAroundTimeNs); + packet->pack(); + + doSendPacket_l(packet, ep); + } + } + + // schedule the next heartbeat + if (mEndpointMap.size()) { + sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats, + handlerID()); + heartbeatMessage->post(kHeartbeatIntervalUs); + } +} + +bool AAH_TXSender::shouldSendHeartbeats_l() { + // assert(holding endpoint lock) + return (systemTime() < (mLastSentPacketTime + kHeartbeatTimeout)); +} + +// Receiver + +// initial 4-byte ID of a retry request packet +const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq'; + +// initial 4-byte ID of a retry NAK packet +const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak'; + +// initial 4-byte ID of a fast start request packet +const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst'; + +AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender) + : Thread(false), + mSender(sender) {} + + AAH_TXSender::RetryReceiver::~RetryReceiver() { + mWakeupEvent.clearPendingEvents(); + } + +// Returns true if val is within the interval bounded inclusively by +// start and end. Also handles the case where there is a rollover of the +// range between start and end. +template <typename T> +static inline bool withinIntervalWithRollover(T val, T start, T end) { + return ((start <= end && val >= start && val <= end) || + (start > end && (val >= start || val <= end))); +} + +bool AAH_TXSender::RetryReceiver::threadLoop() { + struct pollfd pollFds[2]; + pollFds[0].fd = mSender->mSocket; + pollFds[0].events = POLLIN; + pollFds[0].revents = 0; + pollFds[1].fd = mWakeupEvent.getWakeupHandle(); + pollFds[1].events = POLLIN; + pollFds[1].revents = 0; + + int pollResult = poll(pollFds, NELEM(pollFds), -1); + if (pollResult == -1) { + ALOGE("%s poll failed", __PRETTY_FUNCTION__); + return false; + } + + if (exitPending()) { + ALOGI("*** %s exiting", __PRETTY_FUNCTION__); + return false; + } + + if (pollFds[0].revents) { + handleRetryRequest(); + } + + return true; +} + +void AAH_TXSender::RetryReceiver::handleRetryRequest() { + ALOGV("*** RX %s start", __PRETTY_FUNCTION__); + + RetryPacket request; + struct sockaddr requestSrcAddr; + socklen_t requestSrcAddrLen = sizeof(requestSrcAddr); + + ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0, + &requestSrcAddr, &requestSrcAddrLen); + if (result == -1) { + ALOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno); + return; + } + + if (static_cast<size_t>(result) < sizeof(RetryPacket)) { + ALOGW("%s short packet received", __PRETTY_FUNCTION__); + return; + } + + uint32_t host_request_id = ntohl(request.id); + if ((host_request_id != kRetryRequestID) && + (host_request_id != kFastStartRequestID)) { + ALOGW("%s received retry request with bogus ID (%08x)", + __PRETTY_FUNCTION__, host_request_id); + return; + } + + Endpoint endpoint(request.endpointIP, request.endpointPort); + + Mutex::Autolock lock(mSender->mEndpointLock); + + EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint); + + if (eps == NULL || eps->retry.isEmpty()) { + // we have no retry buffer or an empty retry buffer for this endpoint, + // so NAK the entire request + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + return; + } + + RetryBuffer& retry = eps->retry; + + uint16_t startSeq = ntohs(request.seqStart); + uint16_t endSeq = ntohs(request.seqEnd); + + uint16_t retryFirstSeq = retry[0]->getSeqNumber(); + uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber(); + + // If this is a fast start, then force the start of the retry to match the + // start of the retransmit ring buffer (unless the end of the retransmit + // ring buffer is already past the point of fast start) + if ((host_request_id == kFastStartRequestID) && + !((startSeq - retryFirstSeq) & 0x8000)) { + startSeq = retryFirstSeq; + } + + int startIndex; + if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) { + startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq); + } else { + startIndex = -1; + } + + int endIndex; + if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) { + endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq); + } else { + endIndex = -1; + } + + if (startIndex == -1 && endIndex == -1) { + // no part of the request range is found in the retry buffer + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + return; + } + + if (startIndex == -1) { + // NAK a subrange at the front of the request range + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + nak.seqEnd = htons(retryFirstSeq - 1); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + + startIndex = 0; + } else if (endIndex == -1) { + // NAK a subrange at the back of the request range + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + nak.seqStart = htons(retryLastSeq + 1); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + + endIndex = retry.size() - 1; + } + + // send the retry packets + for (int i = startIndex; i <= endIndex; i++) { + const sp<TRTPPacket>& replyPacket = retry[i]; + + result = sendto(mSender->mSocket, + replyPacket->getPacket(), + replyPacket->getPacketLen(), + 0, + &requestSrcAddr, + requestSrcAddrLen); + + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + } +} + +// Endpoint + +AAH_TXSender::Endpoint::Endpoint() + : addr(0) + , port(0) { } + +AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p) + : addr(a) + , port(p) {} + +bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const { + return ((addr < other.addr) || + (addr == other.addr && port < other.port)); +} + +// EndpointState + +AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch) + : retry(kRetryBufferCapacity) + , playerRefCount(1) + , trtpSeqNumber(0) + , nextProgramID(0) + , epoch(_epoch) { } + +// CircularBuffer + +template <typename T> +CircularBuffer<T>::CircularBuffer(size_t capacity) + : mCapacity(capacity) + , mHead(0) + , mTail(0) + , mFillCount(0) { + mBuffer = new T[capacity]; +} + +template <typename T> +CircularBuffer<T>::~CircularBuffer() { + delete [] mBuffer; +} + +template <typename T> +void CircularBuffer<T>::push_back(const T& item) { + if (this->isFull()) { + this->pop_front(); + } + mBuffer[mHead] = item; + mHead = (mHead + 1) % mCapacity; + mFillCount++; +} + +template <typename T> +void CircularBuffer<T>::pop_front() { + CHECK(!isEmpty()); + mBuffer[mTail] = T(); + mTail = (mTail + 1) % mCapacity; + mFillCount--; +} + +template <typename T> +size_t CircularBuffer<T>::size() const { + return mFillCount; +} + +template <typename T> +bool CircularBuffer<T>::isFull() const { + return (mFillCount == mCapacity); +} + +template <typename T> +bool CircularBuffer<T>::isEmpty() const { + return (mFillCount == 0); +} + +template <typename T> +const T& CircularBuffer<T>::itemAt(size_t index) const { + CHECK(index < mFillCount); + return mBuffer[(mTail + index) % mCapacity]; +} + +template <typename T> +const T& CircularBuffer<T>::operator[](size_t index) const { + return itemAt(index); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h new file mode 100644 index 0000000..74206c4 --- /dev/null +++ b/media/libaah_rtp/aah_tx_sender.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __AAH_TX_SENDER_H__ +#define __AAH_TX_SENDER_H__ + +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AHandlerReflector.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +#include "aah_tx_packet.h" +#include "pipe_event.h" + +namespace android { + +template <typename T> class CircularBuffer { + public: + CircularBuffer(size_t capacity); + ~CircularBuffer(); + void push_back(const T& item);; + void pop_front(); + size_t size() const; + bool isFull() const; + bool isEmpty() const; + const T& itemAt(size_t index) const; + const T& operator[](size_t index) const; + + private: + T* mBuffer; + size_t mCapacity; + size_t mHead; + size_t mTail; + size_t mFillCount; + + DISALLOW_EVIL_CONSTRUCTORS(CircularBuffer); +}; + +class AAH_TXSender : public virtual RefBase { + public: + ~AAH_TXSender(); + + static sp<AAH_TXSender> GetInstance(); + + ALooper::handler_id handlerID() { return mReflector->id(); } + + // an IP address and port + struct Endpoint { + Endpoint(); + Endpoint(uint32_t a, uint16_t p); + bool operator<(const Endpoint& other) const; + + uint32_t addr; + uint16_t port; + }; + + uint16_t registerEndpoint(const Endpoint& endpoint); + void unregisterEndpoint(const Endpoint& endpoint); + + enum { + kWhatSendPacket, + kWhatTrimRetryBuffers, + kWhatSendHeartbeats, + }; + + // fields for SendPacket messages + static const char* kSendPacketIPAddr; + static const char* kSendPacketPort; + static const char* kSendPacketTRTPPacket; + + private: + AAH_TXSender(); + + static Mutex sLock; + static wp<AAH_TXSender> sInstance; + static uint32_t sNextEpoch; + static bool sNextEpochValid; + + static uint32_t getNextEpoch(); + + typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer; + + // state maintained on a per-endpoint basis + struct EndpointState { + EndpointState(uint32_t epoch); + RetryBuffer retry; + int playerRefCount; + uint16_t trtpSeqNumber; + uint16_t nextProgramID; + uint32_t epoch; + }; + + friend class AHandlerReflector<AAH_TXSender>; + void onMessageReceived(const sp<AMessage>& msg); + void onSendPacket(const sp<AMessage>& msg); + void doSendPacket_l(const sp<TRTPPacket>& packet, + const Endpoint& endpoint); + void trimRetryBuffers(); + void sendHeartbeats(); + bool shouldSendHeartbeats_l(); + + sp<ALooper> mLooper; + sp<AHandlerReflector<AAH_TXSender> > mReflector; + + int mSocket; + nsecs_t mLastSentPacketTime; + + DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap; + Mutex mEndpointLock; + + static const int kRetryTrimIntervalUs; + static const int kHeartbeatIntervalUs; + static const int kRetryBufferCapacity; + static const nsecs_t kHeartbeatTimeout; + + class RetryReceiver : public Thread { + private: + friend class AAH_TXSender; + + RetryReceiver(AAH_TXSender* sender); + virtual ~RetryReceiver(); + virtual bool threadLoop(); + void handleRetryRequest(); + + static const int kMaxReceiverPacketLen; + static const uint32_t kRetryRequestID; + static const uint32_t kFastStartRequestID; + static const uint32_t kRetryNakID; + + AAH_TXSender* mSender; + PipeEvent mWakeupEvent; + }; + + sp<RetryReceiver> mRetryReceiver; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_TXSender); +}; + +struct RetryPacket { + uint32_t id; + uint32_t endpointIP; + uint16_t endpointPort; + uint16_t seqStart; + uint16_t seqEnd; +} __attribute__((packed)); + +} // namespace android + +#endif // __AAH_TX_SENDER_H__ diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp new file mode 100644 index 0000000..b8e6960 --- /dev/null +++ b/media/libaah_rtp/pipe_event.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +#include "pipe_event.h" + +namespace android { + +PipeEvent::PipeEvent() { + pipe_[0] = -1; + pipe_[1] = -1; + + // Create the pipe. + if (pipe(pipe_) >= 0) { + // Set non-blocking mode on the read side of the pipe so we can + // easily drain it whenever we wakeup. + fcntl(pipe_[0], F_SETFL, O_NONBLOCK); + } else { + ALOGE("Failed to create pipe event %d %d %d", + pipe_[0], pipe_[1], errno); + pipe_[0] = -1; + pipe_[1] = -1; + } +} + +PipeEvent::~PipeEvent() { + if (pipe_[0] >= 0) { + close(pipe_[0]); + } + + if (pipe_[1] >= 0) { + close(pipe_[1]); + } +} + +void PipeEvent::clearPendingEvents() { + char drain_buffer[16]; + while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) { + // No body. + } +} + +bool PipeEvent::wait(int timeout) { + struct pollfd wait_fd; + + wait_fd.fd = getWakeupHandle(); + wait_fd.events = POLLIN; + wait_fd.revents = 0; + + int res = poll(&wait_fd, 1, timeout); + + if (res < 0) { + ALOGE("Wait error in PipeEvent; sleeping to prevent overload!"); + usleep(1000); + } + + return (res > 0); +} + +void PipeEvent::setEvent() { + char foo = 'q'; + write(pipe_[1], &foo, 1); +} + +} // namespace android + diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h new file mode 100644 index 0000000..e53b0fd --- /dev/null +++ b/media/libaah_rtp/pipe_event.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef __PIPE_EVENT_H__ +#define __PIPE_EVENT_H__ + +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +class PipeEvent { + public: + PipeEvent(); + ~PipeEvent(); + + bool initCheck() const { + return ((pipe_[0] >= 0) && (pipe_[1] >= 0)); + } + + int getWakeupHandle() const { return pipe_[0]; } + + // block until the event fires; returns true if the event fired and false if + // the wait timed out. Timeout is expressed in milliseconds; negative + // values mean wait forever. + bool wait(int timeout = -1); + + void clearPendingEvents(); + void setEvent(); + + private: + int pipe_[2]; + + DISALLOW_EVIL_CONSTRUCTORS(PipeEvent); +}; + +} // namespace android + +#endif // __PIPE_EVENT_H__ diff --git a/media/libeffects/data/audio_effects.conf b/media/libeffects/data/audio_effects.conf index b8fa487..ce25bc8 100644 --- a/media/libeffects/data/audio_effects.conf +++ b/media/libeffects/data/audio_effects.conf @@ -50,11 +50,11 @@ effects { } volume { library bundle - uuid 119341a0-8469-11df-81f9- 0002a5d5c51b + uuid 119341a0-8469-11df-81f9-0002a5d5c51b } reverb_env_aux { library reverb - uuid 4a387fc0-8ab3-11df-8bad- 0002a5d5c51b + uuid 4a387fc0-8ab3-11df-8bad-0002a5d5c51b } reverb_env_ins { library reverb diff --git a/media/libeffects/downmix/Android.mk b/media/libeffects/downmix/Android.mk new file mode 100644 index 0000000..0348e1e --- /dev/null +++ b/media/libeffects/downmix/Android.mk @@ -0,0 +1,28 @@ +LOCAL_PATH:= $(call my-dir) + +# Multichannel downmix effect library +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + EffectDownmix.c + +LOCAL_SHARED_LIBRARIES := \ + libcutils + +LOCAL_MODULE:= libdownmix + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx + +ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) +LOCAL_LDLIBS += -ldl +endif + +LOCAL_C_INCLUDES := \ + system/media/audio_effects/include \ + system/media/audio_utils/include + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libeffects/downmix/EffectDownmix.c b/media/libeffects/downmix/EffectDownmix.c new file mode 100644 index 0000000..a325172 --- /dev/null +++ b/media/libeffects/downmix/EffectDownmix.c @@ -0,0 +1,889 @@ +/* + * Copyright (C) 2012 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. + */ + +#define LOG_TAG "EffectDownmix" +#define LOG_NDEBUG 0 +#include <cutils/log.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include "EffectDownmix.h" + +#define MINUS_3_DB_IN_Q19_12 2896 // -3dB = 0.707 * 2^12 = 2896 + +// effect_handle_t interface implementation for downmix effect +const struct effect_interface_s gDownmixInterface = { + Downmix_Process, + Downmix_Command, + Downmix_GetDescriptor, + NULL /* no process_reverse function, no reference stream needed */ +}; + +audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { + tag : AUDIO_EFFECT_LIBRARY_TAG, + version : EFFECT_LIBRARY_API_VERSION, + name : "Downmix Library", + implementor : "The Android Open Source Project", + query_num_effects : DownmixLib_QueryNumberEffects, + query_effect : DownmixLib_QueryEffect, + create_effect : DownmixLib_Create, + release_effect : DownmixLib_Release, + get_descriptor : DownmixLib_GetDescriptor, +}; + + +// AOSP insert downmix UUID: 93f04452-e4fe-41cc-91f9-e475b6d1d69f +static const effect_descriptor_t gDownmixDescriptor = { + EFFECT_UIID_DOWNMIX__, //type + {0x93f04452, 0xe4fe, 0x41cc, 0x91f9, {0xe4, 0x75, 0xb6, 0xd1, 0xd6, 0x9f}}, // uuid + EFFECT_CONTROL_API_VERSION, + EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST, + 0, //FIXME what value should be reported? // cpu load + 0, //FIXME what value should be reported? // memory usage + "Multichannel Downmix To Stereo", // human readable effect name + "The Android Open Source Project" // human readable effect implementor name +}; + +// gDescriptors contains pointers to all defined effect descriptor in this library +static const effect_descriptor_t * const gDescriptors[] = { + &gDownmixDescriptor +}; + +// number of effects in this library +const int kNbEffects = sizeof(gDescriptors) / sizeof(const effect_descriptor_t *); + + +/*---------------------------------------------------------------------------- + * Effect API implementation + *--------------------------------------------------------------------------*/ + +/*--- Effect Library Interface Implementation ---*/ + +int32_t DownmixLib_QueryNumberEffects(uint32_t *pNumEffects) { + ALOGV("DownmixLib_QueryNumberEffects()"); + *pNumEffects = kNbEffects; + return 0; +} + +int32_t DownmixLib_QueryEffect(uint32_t index, effect_descriptor_t *pDescriptor) { + ALOGV("DownmixLib_QueryEffect() index=%d", index); + if (pDescriptor == NULL) { + return -EINVAL; + } + if (index >= (uint32_t)kNbEffects) { + return -EINVAL; + } + memcpy(pDescriptor, gDescriptors[index], sizeof(effect_descriptor_t)); + return 0; +} + + +int32_t DownmixLib_Create(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle) { + int ret; + int i; + downmix_module_t *module; + const effect_descriptor_t *desc; + + ALOGV("DownmixLib_Create()"); + + if (pHandle == NULL || uuid == NULL) { + return -EINVAL; + } + + for (i = 0 ; i < kNbEffects ; i++) { + desc = gDescriptors[i]; + if (memcmp(uuid, &desc->uuid, sizeof(effect_uuid_t)) == 0) { + break; + } + } + + if (i == kNbEffects) { + return -ENOENT; + } + + module = malloc(sizeof(downmix_module_t)); + + module->itfe = &gDownmixInterface; + + module->context.state = DOWNMIX_STATE_UNINITIALIZED; + + ret = Downmix_Init(module); + if (ret < 0) { + ALOGW("DownmixLib_Create() init failed"); + free(module); + return ret; + } + + *pHandle = (effect_handle_t) module; + + ALOGV("DownmixLib_Create() %p , size %d", module, sizeof(downmix_module_t)); + + return 0; +} + + +int32_t DownmixLib_Release(effect_handle_t handle) { + downmix_module_t *pDwmModule = (downmix_module_t *)handle; + + ALOGV("DownmixLib_Release() %p", handle); + if (handle == NULL) { + return -EINVAL; + } + + pDwmModule->context.state = DOWNMIX_STATE_UNINITIALIZED; + + free(pDwmModule); + return 0; +} + + +int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor) { + ALOGV("DownmixLib_GetDescriptor()"); + int i; + + if (pDescriptor == NULL || uuid == NULL){ + ALOGE("DownmixLib_Create() called with NULL pointer"); + return -EINVAL; + } + ALOGV("DownmixLib_GetDescriptor() nb effects=%d", kNbEffects); + for (i = 0; i < kNbEffects; i++) { + ALOGV("DownmixLib_GetDescriptor() i=%d", i); + if (memcmp(uuid, &gDescriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) { + memcpy(pDescriptor, gDescriptors[i], sizeof(effect_descriptor_t)); + ALOGV("EffectGetDescriptor - UUID matched downmix type %d, UUID = %x", + i, gDescriptors[i]->uuid.timeLow); + return 0; + } + } + + return -EINVAL; +} + + +/*--- Effect Control Interface Implementation ---*/ + +static int Downmix_Process(effect_handle_t self, + audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) { + + downmix_object_t *pDownmixer; + int16_t *pSrc, *pDst; + downmix_module_t *pDwmModule = (downmix_module_t *)self; + + if (pDwmModule == NULL) { + return -EINVAL; + } + + if (inBuffer == NULL || inBuffer->raw == NULL || + outBuffer == NULL || outBuffer->raw == NULL || + inBuffer->frameCount != outBuffer->frameCount) { + return -EINVAL; + } + + pDownmixer = (downmix_object_t*) &pDwmModule->context; + + if (pDownmixer->state == DOWNMIX_STATE_UNINITIALIZED) { + ALOGE("Downmix_Process error: trying to use an uninitialized downmixer"); + return -EINVAL; + } else if (pDownmixer->state == DOWNMIX_STATE_INITIALIZED) { + ALOGE("Downmix_Process error: trying to use a non-configured downmixer"); + return -ENODATA; + } + + pSrc = inBuffer->s16; + pDst = outBuffer->s16; + size_t numFrames = outBuffer->frameCount; + + const bool accumulate = + (pDwmModule->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE); + + switch(pDownmixer->type) { + + case DOWNMIX_TYPE_STRIP: + if (accumulate) { + while (numFrames) { + pDst[0] = clamp16(pDst[0] + pSrc[0]); + pDst[1] = clamp16(pDst[1] + pSrc[1]); + pSrc += pDownmixer->input_channel_count; + pDst += 2; + numFrames--; + } + } else { + while (numFrames) { + pDst[0] = pSrc[0]; + pDst[1] = pSrc[1]; + pSrc += pDownmixer->input_channel_count; + pDst += 2; + numFrames--; + } + } + break; + + case DOWNMIX_TYPE_FOLD: + // optimize for the common formats + switch(pDwmModule->config.inputCfg.channels) { + case AUDIO_CHANNEL_OUT_QUAD: + Downmix_foldFromQuad(pSrc, pDst, numFrames, accumulate); + break; + case AUDIO_CHANNEL_OUT_SURROUND: + Downmix_foldFromSurround(pSrc, pDst, numFrames, accumulate); + break; + case AUDIO_CHANNEL_OUT_5POINT1: + Downmix_foldFrom5Point1(pSrc, pDst, numFrames, accumulate); + break; + case AUDIO_CHANNEL_OUT_7POINT1: + Downmix_foldFrom7Point1(pSrc, pDst, numFrames, accumulate); + break; + default: + // FIXME implement generic downmix + ALOGE("Multichannel configurations other than quad, 4.0, 5.1 and 7.1 are not supported"); + break; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + + +static int Downmix_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { + + downmix_module_t *pDwmModule = (downmix_module_t *) self; + downmix_object_t *pDownmixer; + int retsize; + + if (pDwmModule == NULL || pDwmModule->context.state == DOWNMIX_STATE_UNINITIALIZED) { + return -EINVAL; + } + + pDownmixer = (downmix_object_t*) &pDwmModule->context; + + ALOGV("Downmix_Command command %d cmdSize %d",cmdCode, cmdSize); + + switch (cmdCode) { + case EFFECT_CMD_INIT: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + *(int *) pReplyData = Downmix_Init(pDwmModule); + break; + + case EFFECT_CMD_SET_CONFIG: + if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) + || pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + *(int *) pReplyData = Downmix_Configure(pDwmModule, + (effect_config_t *)pCmdData, false); + break; + + case EFFECT_CMD_RESET: + Downmix_Reset(pDownmixer, false); + break; + + case EFFECT_CMD_GET_PARAM: + ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM pCmdData %p, *replySize %d, pReplyData: %p", + pCmdData, *replySize, pReplyData); + if (pCmdData == NULL || cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)) || + pReplyData == NULL || + *replySize < (int) sizeof(effect_param_t) + 2 * sizeof(int32_t)) { + return -EINVAL; + } + effect_param_t *rep = (effect_param_t *) pReplyData; + memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(int32_t)); + ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM param %d, replySize %d", + *(int32_t *)rep->data, rep->vsize); + rep->status = Downmix_getParameter(pDownmixer, *(int32_t *)rep->data, &rep->vsize, + rep->data + sizeof(int32_t)); + *replySize = sizeof(effect_param_t) + sizeof(int32_t) + rep->vsize; + break; + + case EFFECT_CMD_SET_PARAM: + ALOGV("Downmix_Command EFFECT_CMD_SET_PARAM cmdSize %d pCmdData %p, *replySize %d, " \ + "pReplyData %p", cmdSize, pCmdData, *replySize, pReplyData); + if (pCmdData == NULL || (cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t))) + || pReplyData == NULL || *replySize != (int)sizeof(int32_t)) { + return -EINVAL; + } + effect_param_t *cmd = (effect_param_t *) pCmdData; + *(int *)pReplyData = Downmix_setParameter(pDownmixer, *(int32_t *)cmd->data, + cmd->vsize, cmd->data + sizeof(int32_t)); + break; + + case EFFECT_CMD_SET_PARAM_DEFERRED: + //FIXME implement + ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_DEFERRED not supported, FIXME"); + break; + + case EFFECT_CMD_SET_PARAM_COMMIT: + //FIXME implement + ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_COMMIT not supported, FIXME"); + break; + + case EFFECT_CMD_ENABLE: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + if (pDownmixer->state != DOWNMIX_STATE_INITIALIZED) { + return -ENOSYS; + } + pDownmixer->state = DOWNMIX_STATE_ACTIVE; + ALOGV("EFFECT_CMD_ENABLE() OK"); + *(int *)pReplyData = 0; + break; + + case EFFECT_CMD_DISABLE: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + if (pDownmixer->state != DOWNMIX_STATE_ACTIVE) { + return -ENOSYS; + } + pDownmixer->state = DOWNMIX_STATE_INITIALIZED; + ALOGV("EFFECT_CMD_DISABLE() OK"); + *(int *)pReplyData = 0; + break; + + case EFFECT_CMD_SET_DEVICE: + if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) { + return -EINVAL; + } + // FIXME change type if playing on headset vs speaker + ALOGV("Downmix_Command EFFECT_CMD_SET_DEVICE: 0x%08x", *(uint32_t *)pCmdData); + break; + + case EFFECT_CMD_SET_VOLUME: { + // audio output is always stereo => 2 channel volumes + if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t) * 2) { + return -EINVAL; + } + // FIXME change volume + ALOGW("Downmix_Command command EFFECT_CMD_SET_VOLUME not supported, FIXME"); + float left = (float)(*(uint32_t *)pCmdData) / (1 << 24); + float right = (float)(*((uint32_t *)pCmdData + 1)) / (1 << 24); + ALOGV("Downmix_Command EFFECT_CMD_SET_VOLUME: left %f, right %f ", left, right); + break; + } + + case EFFECT_CMD_SET_AUDIO_MODE: + if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) { + return -EINVAL; + } + ALOGV("Downmix_Command EFFECT_CMD_SET_AUDIO_MODE: %d", *(uint32_t *)pCmdData); + break; + + case EFFECT_CMD_SET_CONFIG_REVERSE: + case EFFECT_CMD_SET_INPUT_DEVICE: + // these commands are ignored by a downmix effect + break; + + default: + ALOGW("Downmix_Command invalid command %d",cmdCode); + return -EINVAL; + } + + return 0; +} + + +int Downmix_GetDescriptor(effect_handle_t self, effect_descriptor_t *pDescriptor) +{ + downmix_module_t *pDwnmxModule = (downmix_module_t *) self; + + if (pDwnmxModule == NULL || + pDwnmxModule->context.state == DOWNMIX_STATE_UNINITIALIZED) { + return -EINVAL; + } + + memcpy(pDescriptor, &gDownmixDescriptor, sizeof(effect_descriptor_t)); + + return 0; +} + + +/*---------------------------------------------------------------------------- + * Downmix internal functions + *--------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------- + * Downmix_Init() + *---------------------------------------------------------------------------- + * Purpose: + * Initialize downmix context and apply default parameters + * + * Inputs: + * pDwmModule pointer to downmix effect module + * + * Outputs: + * + * Returns: + * 0 indicates success + * + * Side Effects: + * updates: + * pDwmModule->context.type + * pDwmModule->context.apply_volume_correction + * pDwmModule->config.inputCfg + * pDwmModule->config.outputCfg + * pDwmModule->config.inputCfg.samplingRate + * pDwmModule->config.outputCfg.samplingRate + * pDwmModule->context.state + * doesn't set: + * pDwmModule->itfe + * + *---------------------------------------------------------------------------- + */ + +int Downmix_Init(downmix_module_t *pDwmModule) { + + ALOGV("Downmix_Init module %p", pDwmModule); + int ret = 0; + + memset(&pDwmModule->context, 0, sizeof(downmix_object_t)); + + pDwmModule->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; + pDwmModule->config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + pDwmModule->config.inputCfg.channels = AUDIO_CHANNEL_OUT_7POINT1; + pDwmModule->config.inputCfg.bufferProvider.getBuffer = NULL; + pDwmModule->config.inputCfg.bufferProvider.releaseBuffer = NULL; + pDwmModule->config.inputCfg.bufferProvider.cookie = NULL; + pDwmModule->config.inputCfg.mask = EFFECT_CONFIG_ALL; + + pDwmModule->config.inputCfg.samplingRate = 44100; + pDwmModule->config.outputCfg.samplingRate = pDwmModule->config.inputCfg.samplingRate; + + // set a default value for the access mode, but should be overwritten by caller + pDwmModule->config.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE; + pDwmModule->config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + pDwmModule->config.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; + pDwmModule->config.outputCfg.bufferProvider.getBuffer = NULL; + pDwmModule->config.outputCfg.bufferProvider.releaseBuffer = NULL; + pDwmModule->config.outputCfg.bufferProvider.cookie = NULL; + pDwmModule->config.outputCfg.mask = EFFECT_CONFIG_ALL; + + ret = Downmix_Configure(pDwmModule, &pDwmModule->config, true); + if (ret != 0) { + ALOGV("Downmix_Init error %d on module %p", ret, pDwmModule); + } else { + pDwmModule->context.state = DOWNMIX_STATE_INITIALIZED; + } + + return ret; +} + + +/*---------------------------------------------------------------------------- + * Downmix_Configure() + *---------------------------------------------------------------------------- + * Purpose: + * Set input and output audio configuration. + * + * Inputs: + * pDwmModule pointer to downmix effect module + * pConfig pointer to effect_config_t structure containing input + * and output audio parameters configuration + * init true if called from init function + * + * Outputs: + * + * Returns: + * 0 indicates success + * + * Side Effects: + * + *---------------------------------------------------------------------------- + */ + +int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init) { + + downmix_object_t *pDownmixer = &pDwmModule->context; + + // Check configuration compatibility with build options, and effect capabilities + if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate + || pConfig->outputCfg.channels != DOWNMIX_OUTPUT_CHANNELS + || pConfig->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT + || pConfig->outputCfg.format != AUDIO_FORMAT_PCM_16_BIT) { + ALOGE("Downmix_Configure error: invalid config"); + return -EINVAL; + } + + memcpy(&pDwmModule->config, pConfig, sizeof(effect_config_t)); + + if (init) { + pDownmixer->type = DOWNMIX_TYPE_FOLD; + pDownmixer->apply_volume_correction = false; + pDownmixer->input_channel_count = 8; // matches default input of AUDIO_CHANNEL_OUT_7POINT1 + } else { + // when configuring the effect, do not allow a blank channel mask + if (pConfig->inputCfg.channels == 0) { + ALOGE("Downmix_Configure error: input channel mask can't be 0"); + return -EINVAL; + } + pDownmixer->input_channel_count = popcount(pConfig->inputCfg.channels); + } + + Downmix_Reset(pDownmixer, init); + + return 0; +} + + +/*---------------------------------------------------------------------------- + * Downmix_Reset() + *---------------------------------------------------------------------------- + * Purpose: + * Reset internal states. + * + * Inputs: + * pDownmixer pointer to downmix context + * init true if called from init function + * + * Outputs: +* + * Returns: + * 0 indicates success + * + * Side Effects: + * + *---------------------------------------------------------------------------- + */ + +int Downmix_Reset(downmix_object_t *pDownmixer, bool init) { + // nothing to do here + return 0; +} + + +/*---------------------------------------------------------------------------- + * Downmix_setParameter() + *---------------------------------------------------------------------------- + * Purpose: + * Set a Downmix parameter + * + * Inputs: + * pDownmixer handle to instance data + * param parameter + * pValue pointer to parameter value + * size value size + * + * Outputs: + * + * Returns: + * 0 indicates success + * + * Side Effects: + * + *---------------------------------------------------------------------------- + */ +int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, size_t size, void *pValue) { + + int16_t value16; + ALOGV("Downmix_setParameter, context %p, param %d, value16 %d, value32 %d", + pDownmixer, param, *(int16_t *)pValue, *(int32_t *)pValue); + + switch (param) { + + case DOWNMIX_PARAM_TYPE: + if (size != sizeof(downmix_type_t)) { + ALOGE("Downmix_setParameter(DOWNMIX_PARAM_TYPE) invalid size %d, should be %d", + size, sizeof(downmix_type_t)); + return -EINVAL; + } + value16 = *(int16_t *)pValue; + ALOGV("set DOWNMIX_PARAM_TYPE, type %d", value16); + if (!((value16 > DOWNMIX_TYPE_INVALID) && (value16 < DOWNMIX_TYPE_LAST))) { + ALOGE("Downmix_setParameter invalid DOWNMIX_PARAM_TYPE value %d", value16); + return -EINVAL; + } else { + pDownmixer->type = (downmix_type_t) value16; + break; + + default: + ALOGE("Downmix_setParameter unknown parameter %d", param); + return -EINVAL; + } +} + + return 0; +} /* end Downmix_setParameter */ + + +/*---------------------------------------------------------------------------- + * Downmix_getParameter() + *---------------------------------------------------------------------------- + * Purpose: + * Get a Downmix parameter + * + * Inputs: + * pDownmixer handle to instance data + * param parameter + * pValue pointer to variable to hold retrieved value + * pSize pointer to value size: maximum size as input + * + * Outputs: + * *pValue updated with parameter value + * *pSize updated with actual value size + * + * Returns: + * 0 indicates success + * + * Side Effects: + * + *---------------------------------------------------------------------------- + */ +int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, size_t *pSize, void *pValue) { + int16_t *pValue16; + + switch (param) { + + case DOWNMIX_PARAM_TYPE: + if (*pSize < sizeof(int16_t)) { + ALOGE("Downmix_getParameter invalid parameter size %d for DOWNMIX_PARAM_TYPE", *pSize); + return -EINVAL; + } + pValue16 = (int16_t *)pValue; + *pValue16 = (int16_t) pDownmixer->type; + *pSize = sizeof(int16_t); + ALOGV("Downmix_getParameter DOWNMIX_PARAM_TYPE is %d", *pValue16); + break; + + default: + ALOGE("Downmix_getParameter unknown parameter %d", param); + return -EINVAL; + } + + return 0; +} /* end Downmix_getParameter */ + + +/*---------------------------------------------------------------------------- + * Downmix_foldFromQuad() + *---------------------------------------------------------------------------- + * Purpose: + * downmix a quad signal to stereo + * + * Inputs: + * pSrc quad audio samples to downmix + * numFrames the number of quad frames to downmix + * + * Outputs: + * pDst downmixed stereo audio samples + * + *---------------------------------------------------------------------------- + */ +void Downmix_foldFromQuad(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { + // sample at index 0 is FL + // sample at index 1 is FR + // sample at index 2 is RL + // sample at index 3 is RR + if (accumulate) { + while (numFrames) { + // FL + RL + pDst[0] = clamp16(pDst[0] + pSrc[0] + pSrc[2]); + // FR + RR + pDst[1] = clamp16(pDst[1] + pSrc[1] + pSrc[3]); + pSrc += 4; + pDst += 2; + numFrames--; + } + } else { // same code as above but without adding and clamping pDst[i] to itself + while (numFrames) { + // FL + RL + pDst[0] = clamp16(pSrc[0] + pSrc[2]); + // FR + RR + pDst[1] = clamp16(pSrc[1] + pSrc[3]); + pSrc += 4; + pDst += 2; + numFrames--; + } + } +} + + +/*---------------------------------------------------------------------------- + * Downmix_foldFromSurround() + *---------------------------------------------------------------------------- + * Purpose: + * downmix a "surround sound" (mono rear) signal to stereo + * + * Inputs: + * pSrc surround signal to downmix + * numFrames the number of surround frames to downmix + * + * Outputs: + * pDst downmixed stereo audio samples + * + *---------------------------------------------------------------------------- + */ +void Downmix_foldFromSurround(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { + int32_t lt, rt, centerPlusRearContrib; // samples in Q19.12 format + // sample at index 0 is FL + // sample at index 1 is FR + // sample at index 2 is FC + // sample at index 3 is RC + if (accumulate) { + while (numFrames) { + // centerPlusRearContrib = FC(-3dB) + RC(-3dB) + centerPlusRearContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusRearContrib + lt = (pSrc[0] << 12) + centerPlusRearContrib; + // FR + centerPlusRearContrib + rt = (pSrc[1] << 12) + centerPlusRearContrib; + pDst[0] = clamp16(pDst[0] + (lt >> 12)); + pDst[1] = clamp16(pDst[1] + (rt >> 12)); + pSrc += 4; + pDst += 2; + numFrames--; + } + } else { // same code as above but without adding and clamping pDst[i] to itself + while (numFrames) { + // centerPlusRearContrib = FC(-3dB) + RC(-3dB) + centerPlusRearContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusRearContrib + lt = (pSrc[0] << 12) + centerPlusRearContrib; + // FR + centerPlusRearContrib + rt = (pSrc[1] << 12) + centerPlusRearContrib; + pDst[0] = clamp16(lt >> 12); + pDst[1] = clamp16(rt >> 12); + pSrc += 4; + pDst += 2; + numFrames--; + } + } +} + + +/*---------------------------------------------------------------------------- + * Downmix_foldFrom5Point1() + *---------------------------------------------------------------------------- + * Purpose: + * downmix a 5.1 signal to stereo + * + * Inputs: + * pSrc 5.1 audio samples to downmix + * numFrames the number of 5.1 frames to downmix + * + * Outputs: + * pDst downmixed stereo audio samples + * + *---------------------------------------------------------------------------- + */ +void Downmix_foldFrom5Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { + int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format + // sample at index 0 is FL + // sample at index 1 is FR + // sample at index 2 is FC + // sample at index 3 is LFE + // sample at index 4 is RL + // sample at index 5 is RR + if (accumulate) { + while (numFrames) { + // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) + centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusLfeContrib + RL + lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12); + // FR + centerPlusLfeContrib + RR + rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12); + pDst[0] = clamp16(pDst[0] + (lt >> 12)); + pDst[1] = clamp16(pDst[1] + (rt >> 12)); + pSrc += 6; + pDst += 2; + numFrames--; + } + } else { // same code as above but without adding and clamping pDst[i] to itself + while (numFrames) { + // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) + centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusLfeContrib + RL + lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12); + // FR + centerPlusLfeContrib + RR + rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12); + pDst[0] = clamp16(lt >> 12); + pDst[1] = clamp16(rt >> 12); + pSrc += 6; + pDst += 2; + numFrames--; + } + } +} + + +/*---------------------------------------------------------------------------- + * Downmix_foldFrom7Point1() + *---------------------------------------------------------------------------- + * Purpose: + * downmix a 7.1 signal to stereo + * + * Inputs: + * pSrc 7.1 audio samples to downmix + * numFrames the number of 7.1 frames to downmix + * + * Outputs: + * pDst downmixed stereo audio samples + * + *---------------------------------------------------------------------------- + */ +void Downmix_foldFrom7Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { + int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format + // sample at index 0 is FL + // sample at index 1 is FR + // sample at index 2 is FC + // sample at index 3 is LFE + // sample at index 4 is RL + // sample at index 5 is RR + // sample at index 6 is SL + // sample at index 7 is SR + if (accumulate) { + while (numFrames) { + // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) + centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusLfeContrib + SL + RL + lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12); + // FR + centerPlusLfeContrib + SR + RR + rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12); + pDst[0] = clamp16(lt >> 12); + pDst[1] = clamp16(rt >> 12); + pSrc += 8; + pDst += 2; + numFrames--; + } + } else { // same code as above but without adding and clamping pDst[i] to itself + while (numFrames) { + // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) + centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + + (pSrc[3] * MINUS_3_DB_IN_Q19_12); + // FL + centerPlusLfeContrib + SL + RL + lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12); + // FR + centerPlusLfeContrib + SR + RR + rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12); + pDst[0] = clamp16(pDst[0] + (lt >> 12)); + pDst[1] = clamp16(pDst[1] + (rt >> 12)); + pSrc += 8; + pDst += 2; + numFrames--; + } + } +} + diff --git a/media/libeffects/downmix/EffectDownmix.h b/media/libeffects/downmix/EffectDownmix.h new file mode 100644 index 0000000..4176b5a --- /dev/null +++ b/media/libeffects/downmix/EffectDownmix.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_EFFECTDOWNMIX_H_ +#define ANDROID_EFFECTDOWNMIX_H_ + +#include <audio_effects/effect_downmix.h> +#include <audio_utils/primitives.h> +#include <system/audio.h> + +/*------------------------------------ + * definitions + *------------------------------------ +*/ + +#define DOWNMIX_OUTPUT_CHANNELS AUDIO_CHANNEL_OUT_STEREO + +typedef enum { + DOWNMIX_STATE_UNINITIALIZED, + DOWNMIX_STATE_INITIALIZED, + DOWNMIX_STATE_ACTIVE, +} downmix_state_t; + +/* parameters for each downmixer */ +typedef struct { + downmix_state_t state; + downmix_type_t type; + bool apply_volume_correction; + uint8_t input_channel_count; +} downmix_object_t; + + +typedef struct downmix_module_s { + const struct effect_interface_s *itfe; + effect_config_t config; + downmix_object_t context; +} downmix_module_t; + + +/*------------------------------------ + * Effect API + *------------------------------------ +*/ +int32_t DownmixLib_QueryNumberEffects(uint32_t *pNumEffects); +int32_t DownmixLib_QueryEffect(uint32_t index, + effect_descriptor_t *pDescriptor); +int32_t DownmixLib_Create(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle); +int32_t DownmixLib_Release(effect_handle_t handle); +int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor); + +static int Downmix_Process(effect_handle_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); +static int Downmix_Command(effect_handle_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); +static int Downmix_GetDescriptor(effect_handle_t self, + effect_descriptor_t *pDescriptor); + + +/*------------------------------------ + * internal functions + *------------------------------------ +*/ +int Downmix_Init(downmix_module_t *pDwmModule); +int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init); +int Downmix_Reset(downmix_object_t *pDownmixer, bool init); +int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, size_t size, void *pValue); +int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, size_t *pSize, void *pValue); + +void Downmix_foldFromQuad(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate); +void Downmix_foldFromSurround(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate); +void Downmix_foldFrom5Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate); +void Downmix_foldFrom7Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate); + +#endif /*ANDROID_EFFECTDOWNMIX_H_*/ diff --git a/media/libeffects/preprocessing/PreProcessing.cpp b/media/libeffects/preprocessing/PreProcessing.cpp index 098a1a2..dc27d38 100755 --- a/media/libeffects/preprocessing/PreProcessing.cpp +++ b/media/libeffects/preprocessing/PreProcessing.cpp @@ -845,6 +845,17 @@ int Session_SetConfig(preproc_session_t *session, effect_config_t *config) config->inputCfg.samplingRate, config->inputCfg.channels); int status; + // if at least one process is enabled, do not accept configuration changes + if (session->enabledMsk) { + if (session->samplingRate != config->inputCfg.samplingRate || + session->inChannelCount != inCnl || + session->outChannelCount != outCnl) { + return -ENOSYS; + } else { + return 0; + } + } + // AEC implementation is limited to 16kHz if (config->inputCfg.samplingRate >= 32000 && !(session->createdMsk & (1 << PREPROC_AEC))) { session->apmSamplingRate = 32000; @@ -1287,7 +1298,9 @@ int PreProcessingFx_Command(effect_handle_t self, if (*(int *)pReplyData != 0) { break; } - *(int *)pReplyData = Effect_SetState(effect, PREPROC_EFFECT_STATE_CONFIG); + if (effect->state != PREPROC_EFFECT_STATE_ACTIVE) { + *(int *)pReplyData = Effect_SetState(effect, PREPROC_EFFECT_STATE_CONFIG); + } break; case EFFECT_CMD_GET_CONFIG: diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index f9f997f..6808aa2 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -202,7 +202,7 @@ bool AudioEffect::getEnabled() const status_t AudioEffect::setEnabled(bool enabled) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus; } status_t status = NO_ERROR; @@ -231,7 +231,7 @@ status_t AudioEffect::command(uint32_t cmdCode, { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { ALOGV("command() bad status %d", mStatus); - return INVALID_OPERATION; + return mStatus; } if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) { @@ -263,7 +263,7 @@ status_t AudioEffect::command(uint32_t cmdCode, status_t AudioEffect::setParameter(effect_param_t *param) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -281,7 +281,7 @@ status_t AudioEffect::setParameter(effect_param_t *param) status_t AudioEffect::setParameterDeferred(effect_param_t *param) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -307,7 +307,7 @@ status_t AudioEffect::setParameterDeferred(effect_param_t *param) status_t AudioEffect::setParameterCommit() { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus; } Mutex::Autolock _l(mCblk->lock); @@ -321,7 +321,7 @@ status_t AudioEffect::setParameterCommit() status_t AudioEffect::getParameter(effect_param_t *param) { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { - return INVALID_OPERATION; + return mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -341,7 +341,7 @@ status_t AudioEffect::getParameter(effect_param_t *param) void AudioEffect::binderDied() { ALOGW("IEffect died"); - mStatus = NO_INIT; + mStatus = DEAD_OBJECT; if (mCbf != NULL) { status_t status = DEAD_OBJECT; mCbf(EVENT_ERROR, mUserData, &status); diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index a4068ff..943f3af 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -307,7 +307,7 @@ status_t AudioRecord::start() pid_t tid; if (t != 0) { mReadyToRun = WOULD_BLOCK; - t->run("ClientRecordThread", ANDROID_PRIORITY_AUDIO); + t->run("AudioRecord", ANDROID_PRIORITY_AUDIO); tid = t->getTid(); // pid_t is unknown until run() ALOGV("getTid=%d", tid); if (tid == -1) { @@ -386,7 +386,7 @@ bool AudioRecord::stopped() const return !mActive; } -uint32_t AudioRecord::getSampleRate() +uint32_t AudioRecord::getSampleRate() const { AutoMutex lock(mLock); return mCblk->sampleRate; @@ -402,7 +402,7 @@ status_t AudioRecord::setMarkerPosition(uint32_t marker) return NO_ERROR; } -status_t AudioRecord::getMarkerPosition(uint32_t *marker) +status_t AudioRecord::getMarkerPosition(uint32_t *marker) const { if (marker == NULL) return BAD_VALUE; @@ -423,7 +423,7 @@ status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod) return NO_ERROR; } -status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) +status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) const { if (updatePeriod == NULL) return BAD_VALUE; @@ -432,7 +432,7 @@ status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) return NO_ERROR; } -status_t AudioRecord::getPosition(uint32_t *position) +status_t AudioRecord::getPosition(uint32_t *position) const { if (position == NULL) return BAD_VALUE; @@ -442,7 +442,7 @@ status_t AudioRecord::getPosition(uint32_t *position) return NO_ERROR; } -unsigned int AudioRecord::getInputFramesLost() +unsigned int AudioRecord::getInputFramesLost() const { if (mActive) return AudioSystem::getInputFramesLost(mInput); @@ -597,7 +597,7 @@ void AudioRecord::releaseBuffer(Buffer* audioBuffer) mCblk->stepUser(audioBuffer->frameCount); } -audio_io_handle_t AudioRecord::getInput() +audio_io_handle_t AudioRecord::getInput() const { AutoMutex lock(mLock); return mInput; @@ -615,7 +615,7 @@ audio_io_handle_t AudioRecord::getInput_l() return mInput; } -int AudioRecord::getSessionId() +int AudioRecord::getSessionId() const { return mSessionId; } diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp index ec4c044..e0b186a 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -405,9 +405,9 @@ void AudioSystem::AudioFlingerClient::binderDied(const wp<IBinder>& who) { } void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle_t ioHandle, - void *param2) { + const void *param2) { ALOGV("ioConfigChanged() event %d", event); - OutputDescriptor *desc; + const OutputDescriptor *desc; audio_stream_type_t stream; if (ioHandle == 0) return; @@ -417,7 +417,7 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle switch (event) { case STREAM_CONFIG_CHANGED: if (param2 == NULL) break; - stream = *(audio_stream_type_t *)param2; + stream = *(const audio_stream_type_t *)param2; ALOGV("ioConfigChanged() STREAM_CONFIG_CHANGED stream %d, output %d", stream, ioHandle); if (gStreamOutputMap.indexOfKey(stream) >= 0) { gStreamOutputMap.replaceValueFor(stream, ioHandle); @@ -429,7 +429,7 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle break; } if (param2 == NULL) break; - desc = (OutputDescriptor *)param2; + desc = (const OutputDescriptor *)param2; OutputDescriptor *outputDesc = new OutputDescriptor(*desc); gOutputs.add(ioHandle, outputDesc); @@ -458,7 +458,7 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle break; } if (param2 == NULL) break; - desc = (OutputDescriptor *)param2; + desc = (const OutputDescriptor *)param2; ALOGV("ioConfigChanged() new config for output %d samplingRate %d, format %d channels %d frameCount %d latency %d", ioHandle, desc->samplingRate, desc->format, diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index aead9a1..a1c99e5 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -80,7 +80,9 @@ status_t AudioTrack::getMinFrameCount( AudioTrack::AudioTrack() : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { } @@ -96,7 +98,9 @@ AudioTrack::AudioTrack( int notificationFrames, int sessionId) : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { mStatus = set(streamType, sampleRate, format, channelMask, frameCount, flags, cbf, user, notificationFrames, @@ -134,7 +138,9 @@ AudioTrack::AudioTrack( int notificationFrames, int sessionId) : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { mStatus = set(streamType, sampleRate, format, channelMask, 0, flags, cbf, user, notificationFrames, @@ -364,7 +370,7 @@ void AudioTrack::start() android_atomic_and(~CBLK_DISABLED_ON, &cblk->flags); pid_t tid; if (t != 0) { - t->run("AudioTrackThread", ANDROID_PRIORITY_AUDIO); + t->run("AudioTrack", ANDROID_PRIORITY_AUDIO); tid = t->getTid(); // pid_t is unknown until run() ALOGV("getTid=%d", tid); if (tid == -1) { @@ -540,6 +546,10 @@ status_t AudioTrack::setSampleRate(int rate) { int afSamplingRate; + if (mIsTimed) { + return INVALID_OPERATION; + } + if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) { return NO_INIT; } @@ -553,6 +563,10 @@ status_t AudioTrack::setSampleRate(int rate) uint32_t AudioTrack::getSampleRate() const { + if (mIsTimed) { + return INVALID_OPERATION; + } + AutoMutex lock(mLock); return mCblk->sampleRate; } @@ -578,6 +592,10 @@ status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCou return NO_ERROR; } + if (mIsTimed) { + return INVALID_OPERATION; + } + if (loopStart >= loopEnd || loopEnd - loopStart > cblk->frameCount || cblk->server > loopStart) { @@ -641,6 +659,8 @@ status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const status_t AudioTrack::setPosition(uint32_t position) { + if (mIsTimed) return INVALID_OPERATION; + AutoMutex lock(mLock); if (!stopped_l()) return INVALID_OPERATION; @@ -764,12 +784,9 @@ status_t AudioTrack::createTrack_l( mNotificationFramesAct = frameCount/2; } if (frameCount < minFrameCount) { - if (enforceFrameCount) { - ALOGE("Invalid buffer size: minFrameCount %d, frameCount %d", minFrameCount, frameCount); - return BAD_VALUE; - } else { - frameCount = minFrameCount; - } + ALOGW_IF(enforceFrameCount, "Minimum buffer size corrected from %d to %d", + frameCount, minFrameCount); + frameCount = minFrameCount; } } else { // Ensure that buffer alignment matches channelCount @@ -791,6 +808,7 @@ status_t AudioTrack::createTrack_l( ((uint16_t)flags) << 16, sharedBuffer, output, + mIsTimed, &mSessionId, &status); @@ -957,6 +975,7 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize) { if (mSharedBuffer != 0) return INVALID_OPERATION; + if (mIsTimed) return INVALID_OPERATION; if (ssize_t(userSize) < 0) { // Sanity-check: user is most-likely passing an error code, and it would @@ -1013,6 +1032,59 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize) // ------------------------------------------------------------------------- +TimedAudioTrack::TimedAudioTrack() { + mIsTimed = true; +} + +status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer) +{ + status_t result = UNKNOWN_ERROR; + + // If the track is not invalid already, try to allocate a buffer. alloc + // fails indicating that the server is dead, flag the track as invalid so + // we can attempt to restore in in just a bit. + if (!(mCblk->flags & CBLK_INVALID_MSK)) { + result = mAudioTrack->allocateTimedBuffer(size, buffer); + if (result == DEAD_OBJECT) { + android_atomic_or(CBLK_INVALID_ON, &mCblk->flags); + } + } + + // If the track is invalid at this point, attempt to restore it. and try the + // allocation one more time. + if (mCblk->flags & CBLK_INVALID_MSK) { + mCblk->lock.lock(); + result = restoreTrack_l(mCblk, false); + mCblk->lock.unlock(); + + if (result == OK) + result = mAudioTrack->allocateTimedBuffer(size, buffer); + } + + return result; +} + +status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) +{ + // restart track if it was disabled by audioflinger due to previous underrun + if (mActive && (mCblk->flags & CBLK_DISABLED_MSK)) { + android_atomic_and(~CBLK_DISABLED_ON, &mCblk->flags); + ALOGW("queueTimedBuffer() track %p disabled, restarting", this); + mAudioTrack->start(0); + } + + return mAudioTrack->queueTimedBuffer(buffer, pts); +} + +status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform, + TargetTimeline target) +{ + return mAudioTrack->setMediaTimeTransform(xform, target); +} + +// ------------------------------------------------------------------------- + bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread) { Buffer audioBuffer; diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp index 4507e5d..ebadbfa 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -90,6 +90,7 @@ public: uint32_t flags, const sp<IMemory>& sharedBuffer, audio_io_handle_t output, + bool isTimed, int *sessionId, status_t *status) { @@ -105,6 +106,7 @@ public: data.writeInt32(flags); data.writeStrongBinder(sharedBuffer->asBinder()); data.writeInt32((int32_t) output); + data.writeInt32(isTimed); int lSessionId = 0; if (sessionId != NULL) { lSessionId = *sessionId; @@ -689,11 +691,12 @@ status_t BnAudioFlinger::onTransact( uint32_t flags = data.readInt32(); sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder()); audio_io_handle_t output = (audio_io_handle_t) data.readInt32(); + bool isTimed = data.readInt32(); int sessionId = data.readInt32(); status_t status; sp<IAudioTrack> track = createTrack(pid, (audio_stream_type_t) streamType, sampleRate, format, - channelCount, bufferCount, flags, buffer, output, &sessionId, &status); + channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status); reply->writeInt32(sessionId); reply->writeInt32(status); reply->writeStrongBinder(track->asBinder()); diff --git a/media/libmedia/IAudioFlingerClient.cpp b/media/libmedia/IAudioFlingerClient.cpp index ce28b33..1db39a3 100644 --- a/media/libmedia/IAudioFlingerClient.cpp +++ b/media/libmedia/IAudioFlingerClient.cpp @@ -39,18 +39,18 @@ public: { } - void ioConfigChanged(int event, audio_io_handle_t ioHandle, void *param2) + void ioConfigChanged(int event, audio_io_handle_t ioHandle, const void *param2) { Parcel data, reply; data.writeInterfaceToken(IAudioFlingerClient::getInterfaceDescriptor()); data.writeInt32(event); data.writeInt32((int32_t) ioHandle); if (event == AudioSystem::STREAM_CONFIG_CHANGED) { - uint32_t stream = *(uint32_t *)param2; + uint32_t stream = *(const uint32_t *)param2; ALOGV("ioConfigChanged stream %d", stream); data.writeInt32(stream); } else if (event != AudioSystem::OUTPUT_CLOSED && event != AudioSystem::INPUT_CLOSED) { - AudioSystem::OutputDescriptor *desc = (AudioSystem::OutputDescriptor *)param2; + const AudioSystem::OutputDescriptor *desc = (const AudioSystem::OutputDescriptor *)param2; data.writeInt32(desc->samplingRate); data.writeInt32(desc->format); data.writeInt32(desc->channels); @@ -73,7 +73,7 @@ status_t BnAudioFlingerClient::onTransact( CHECK_INTERFACE(IAudioFlingerClient, data, reply); int event = data.readInt32(); audio_io_handle_t ioHandle = (audio_io_handle_t) data.readInt32(); - void *param2 = NULL; + const void *param2 = NULL; AudioSystem::OutputDescriptor desc; uint32_t stream; if (event == AudioSystem::STREAM_CONFIG_CHANGED) { diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp index a7958de..28ebbbf 100644 --- a/media/libmedia/IAudioTrack.cpp +++ b/media/libmedia/IAudioTrack.cpp @@ -35,7 +35,10 @@ enum { FLUSH, MUTE, PAUSE, - ATTACH_AUX_EFFECT + ATTACH_AUX_EFFECT, + ALLOCATE_TIMED_BUFFER, + QUEUE_TIMED_BUFFER, + SET_MEDIA_TIME_TRANSFORM, }; class BpAudioTrack : public BpInterface<IAudioTrack> @@ -114,6 +117,52 @@ public: } return status; } + + virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeInt32(size); + status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + if (status == NO_ERROR) { + *buffer = interface_cast<IMemory>(reply.readStrongBinder()); + } + } + return status; + } + + virtual status_t queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeStrongBinder(buffer->asBinder()); + data.writeInt64(pts); + status_t status = remote()->transact(QUEUE_TIMED_BUFFER, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + } + return status; + } + + virtual status_t setMediaTimeTransform(const LinearTransform& xform, + int target) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeInt64(xform.a_zero); + data.writeInt64(xform.b_zero); + data.writeInt32(xform.a_to_b_numer); + data.writeInt32(xform.a_to_b_denom); + data.writeInt32(target); + status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + } + return status; + } }; IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack"); @@ -159,10 +208,38 @@ status_t BnAudioTrack::onTransact( reply->writeInt32(attachAuxEffect(data.readInt32())); return NO_ERROR; } break; + case ALLOCATE_TIMED_BUFFER: { + CHECK_INTERFACE(IAudioTrack, data, reply); + sp<IMemory> buffer; + status_t status = allocateTimedBuffer(data.readInt32(), &buffer); + reply->writeInt32(status); + if (status == NO_ERROR) { + reply->writeStrongBinder(buffer->asBinder()); + } + return NO_ERROR; + } break; + case QUEUE_TIMED_BUFFER: { + CHECK_INTERFACE(IAudioTrack, data, reply); + sp<IMemory> buffer = interface_cast<IMemory>( + data.readStrongBinder()); + uint64_t pts = data.readInt64(); + reply->writeInt32(queueTimedBuffer(buffer, pts)); + return NO_ERROR; + } break; + case SET_MEDIA_TIME_TRANSFORM: { + CHECK_INTERFACE(IAudioTrack, data, reply); + LinearTransform xform; + xform.a_zero = data.readInt64(); + xform.b_zero = data.readInt64(); + xform.a_to_b_numer = data.readInt32(); + xform.a_to_b_denom = data.readInt32(); + int target = data.readInt32(); + reply->writeInt32(setMediaTimeTransform(xform, target)); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } } }; // namespace android - diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp index d469e28..5d40cc8 100644 --- a/media/libmedia/IEffect.cpp +++ b/media/libmedia/IEffect.cpp @@ -83,8 +83,15 @@ public: size = *pReplySize; } data.writeInt32(size); - remote()->transact(COMMAND, data, &reply); - status_t status = reply.readInt32(); + + status_t status = remote()->transact(COMMAND, data, &reply); + if (status != NO_ERROR) { + if (pReplySize != NULL) + *pReplySize = 0; + return status; + } + + status = reply.readInt32(); size = reply.readInt32(); if (size != 0 && pReplyData != NULL && pReplySize != NULL) { reply.read(pReplyData, size); diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp index 64cc919..c47fa41 100644 --- a/media/libmedia/IMediaPlayer.cpp +++ b/media/libmedia/IMediaPlayer.cpp @@ -15,6 +15,7 @@ ** limitations under the License. */ +#include <arpa/inet.h> #include <stdint.h> #include <sys/types.h> @@ -23,8 +24,6 @@ #include <media/IMediaPlayer.h> #include <media/IStreamSource.h> -#include <surfaceflinger/ISurface.h> -#include <surfaceflinger/Surface.h> #include <gui/ISurfaceTexture.h> #include <utils/String8.h> @@ -55,6 +54,7 @@ enum { SET_VIDEO_SURFACETEXTURE, SET_PARAMETER, GET_PARAMETER, + SET_RETRANSMIT_ENDPOINT, }; class BpMediaPlayer: public BpInterface<IMediaPlayer> @@ -291,6 +291,25 @@ public: return remote()->transact(GET_PARAMETER, data, reply); } + status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) { + Parcel data, reply; + status_t err; + + data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor()); + if (NULL != endpoint) { + data.writeInt32(sizeof(*endpoint)); + data.write(endpoint, sizeof(*endpoint)); + } else { + data.writeInt32(0); + } + + err = remote()->transact(SET_RETRANSMIT_ENDPOINT, data, &reply); + if (OK != err) { + return err; + } + + return reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(MediaPlayer, "android.media.IMediaPlayer"); @@ -459,6 +478,20 @@ status_t BnMediaPlayer::onTransact( CHECK_INTERFACE(IMediaPlayer, data, reply); return getParameter(data.readInt32(), reply); } break; + case SET_RETRANSMIT_ENDPOINT: { + CHECK_INTERFACE(IMediaPlayer, data, reply); + + struct sockaddr_in endpoint; + int amt = data.readInt32(); + if (amt == sizeof(endpoint)) { + data.read(&endpoint, sizeof(struct sockaddr_in)); + reply->writeInt32(setRetransmitEndpoint(&endpoint)); + } else { + reply->writeInt32(setRetransmitEndpoint(NULL)); + } + + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index 42f55c2..2f4e31a 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -19,10 +19,10 @@ #define LOG_TAG "IMediaRecorder" #include <utils/Log.h> #include <binder/Parcel.h> -#include <surfaceflinger/Surface.h> #include <camera/ICamera.h> #include <media/IMediaRecorderClient.h> #include <media/IMediaRecorder.h> +#include <gui/Surface.h> #include <gui/ISurfaceTexture.h> #include <unistd.h> diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp index 27c7e03..48e427a 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -22,8 +22,6 @@ #include <binder/Parcel.h> #include <media/IOMX.h> #include <media/stagefright/foundation/ADebug.h> -#include <surfaceflinger/ISurface.h> -#include <surfaceflinger/Surface.h> namespace android { diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp index 13b64e9..70f8c0c 100644 --- a/media/libmedia/Visualizer.cpp +++ b/media/libmedia/Visualizer.cpp @@ -168,7 +168,7 @@ status_t Visualizer::getWaveForm(uint8_t *waveform) uint32_t replySize = mCaptureSize; status = command(VISUALIZER_CMD_CAPTURE, 0, NULL, &replySize, waveform); ALOGV("getWaveForm() command returned %d", status); - if (replySize == 0) { + if ((status == NO_ERROR) && (replySize == 0)) { status = NOT_ENOUGH_DATA; } } else { diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp index 250425b..4ff1862 100644 --- a/media/libmedia/mediaplayer.cpp +++ b/media/libmedia/mediaplayer.cpp @@ -32,8 +32,6 @@ #include <media/mediaplayer.h> #include <media/AudioSystem.h> -#include <surfaceflinger/Surface.h> - #include <binder/MemoryBase.h> #include <utils/KeyedVector.h> @@ -63,6 +61,7 @@ MediaPlayer::MediaPlayer() mAudioSessionId = AudioSystem::newAudioSessionId(); AudioSystem::acquireAudioSessionId(mAudioSessionId); mSendLevel = 0; + mRetransmitEndpointValid = false; } MediaPlayer::~MediaPlayer() @@ -95,6 +94,7 @@ void MediaPlayer::clear_l() mCurrentPosition = -1; mSeekPosition = -1; mVideoWidth = mVideoHeight = 0; + mRetransmitEndpointValid = false; } status_t MediaPlayer::setListener(const sp<MediaPlayerListener>& listener) @@ -146,7 +146,8 @@ status_t MediaPlayer::setDataSource( const sp<IMediaPlayerService>& service(getMediaPlayerService()); if (service != 0) { sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId)); - if (NO_ERROR != player->setDataSource(url, headers)) { + if ((NO_ERROR != doSetRetransmitEndpoint(player)) || + (NO_ERROR != player->setDataSource(url, headers))) { player.clear(); } err = attachNewPlayer(player); @@ -162,7 +163,8 @@ status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length) const sp<IMediaPlayerService>& service(getMediaPlayerService()); if (service != 0) { sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId)); - if (NO_ERROR != player->setDataSource(fd, offset, length)) { + if ((NO_ERROR != doSetRetransmitEndpoint(player)) || + (NO_ERROR != player->setDataSource(fd, offset, length))) { player.clear(); } err = attachNewPlayer(player); @@ -177,7 +179,8 @@ status_t MediaPlayer::setDataSource(const sp<IStreamSource> &source) const sp<IMediaPlayerService>& service(getMediaPlayerService()); if (service != 0) { sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId)); - if (NO_ERROR != player->setDataSource(source)) { + if ((NO_ERROR != doSetRetransmitEndpoint(player)) || + (NO_ERROR != player->setDataSource(source))) { player.clear(); } err = attachNewPlayer(player); @@ -471,6 +474,20 @@ status_t MediaPlayer::reset_l() return NO_ERROR; } +status_t MediaPlayer::doSetRetransmitEndpoint(const sp<IMediaPlayer>& player) { + Mutex::Autolock _l(mLock); + + if (player == NULL) { + return UNKNOWN_ERROR; + } + + if (mRetransmitEndpointValid) { + return player->setRetransmitEndpoint(&mRetransmitEndpoint); + } + + return OK; +} + status_t MediaPlayer::reset() { ALOGV("reset"); @@ -599,6 +616,34 @@ status_t MediaPlayer::getParameter(int key, Parcel *reply) return INVALID_OPERATION; } +status_t MediaPlayer::setRetransmitEndpoint(const char* addrString, + uint16_t port) { + ALOGV("MediaPlayer::setRetransmitEndpoint(%s:%hu)", + addrString ? addrString : "(null)", port); + + Mutex::Autolock _l(mLock); + if ((mPlayer != NULL) || (mCurrentState != MEDIA_PLAYER_IDLE)) + return INVALID_OPERATION; + + if (NULL == addrString) { + mRetransmitEndpointValid = false; + return OK; + } + + struct in_addr saddr; + if(!inet_aton(addrString, &saddr)) { + return BAD_VALUE; + } + + memset(&mRetransmitEndpoint, 0, sizeof(&mRetransmitEndpoint)); + mRetransmitEndpoint.sin_family = AF_INET; + mRetransmitEndpoint.sin_addr = saddr; + mRetransmitEndpoint.sin_port = htons(port); + mRetransmitEndpointValid = true; + + return OK; +} + void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj) { ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2); diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 8d947d8..cc73014 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -18,7 +18,6 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaRecorder" #include <utils/Log.h> -#include <surfaceflinger/Surface.h> #include <media/mediarecorder.h> #include <binder/IServiceManager.h> #include <utils/String8.h> diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index a3e2517..e521648 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -29,7 +29,8 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright_omx \ libstagefright_foundation \ libgui \ - libdl + libdl \ + libaah_rtp LOCAL_STATIC_LIBRARIES := \ libstagefright_nuplayer \ diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 4df7f3d..1a85c9c 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -70,6 +70,11 @@ #include <OMX.h> +namespace android { +sp<MediaPlayerBase> createAAH_TXPlayer(); +sp<MediaPlayerBase> createAAH_RXPlayer(); +} + namespace { using android::media::Metadata; using android::status_t; @@ -320,7 +325,7 @@ status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector<String16>& a mStreamType, mLeftVolume, mRightVolume); result.append(buffer); snprintf(buffer, 255, " msec per frame(%f), latency (%d)\n", - mMsecsPerFrame, mLatency); + mMsecsPerFrame, (mTrack != 0) ? mTrack->latency() : -1); result.append(buffer); snprintf(buffer, 255, " aux effect id(%d), send level (%f)\n", mAuxEffectId, mSendLevel); @@ -487,6 +492,7 @@ MediaPlayerService::Client::Client( mStatus = NO_INIT; mAudioSessionId = audioSessionId; mUID = uid; + mRetransmitEndpointValid = false; #if CALLBACK_ANTAGONIZER ALOGD("create Antagonizer"); @@ -593,6 +599,10 @@ player_type getPlayerType(const char* url) return NU_PLAYER; } + if (!strncasecmp("aahRX://", url, 8)) { + return AAH_RX_PLAYER; + } + // use MidiFile for MIDI extensions int lenURL = strlen(url); for (int i = 0; i < NELEM(FILE_EXTS); ++i) { @@ -608,6 +618,44 @@ player_type getPlayerType(const char* url) return getDefaultPlayerType(); } +player_type MediaPlayerService::Client::getPlayerType(int fd, + int64_t offset, + int64_t length) +{ + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return android::getPlayerType(fd, offset, length); +} + +player_type MediaPlayerService::Client::getPlayerType(const char* url) +{ + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return android::getPlayerType(url); +} + +player_type MediaPlayerService::Client::getPlayerType( + const sp<IStreamSource> &source) { + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return NU_PLAYER; +} + static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, notify_callback_f notifyFunc) { @@ -629,6 +677,14 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, ALOGV("Create Test Player stub"); p = new TestPlayerStub(); break; + case AAH_RX_PLAYER: + ALOGV(" create A@H RX Player"); + p = createAAH_RXPlayer(); + break; + case AAH_TX_PLAYER: + ALOGV(" create A@H TX Player"); + p = createAAH_TXPlayer(); + break; default: ALOGE("Unknown player type: %d", playerType); return NULL; @@ -665,6 +721,49 @@ sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerT return p; } +sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre( + player_type playerType) +{ + ALOGV("player type = %d", playerType); + + // create the right type of player + sp<MediaPlayerBase> p = createPlayer(playerType); + if (p == NULL) { + return p; + } + + if (!p->hardwareOutput()) { + mAudioOutput = new AudioOutput(mAudioSessionId); + static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); + } + + return p; +} + +void MediaPlayerService::Client::setDataSource_post( + const sp<MediaPlayerBase>& p, + status_t status) +{ + ALOGV(" setDataSource"); + mStatus = status; + if (mStatus != OK) { + ALOGE(" error: %d", mStatus); + return; + } + + // Set the re-transmission endpoint if one was chosen. + if (mRetransmitEndpointValid) { + mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint); + if (mStatus != NO_ERROR) { + ALOGE("setRetransmitEndpoint error: %d", mStatus); + } + } + + if (mStatus == OK) { + mPlayer = p; + } +} + status_t MediaPlayerService::Client::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { @@ -696,25 +795,12 @@ status_t MediaPlayerService::Client::setDataSource( return mStatus; } else { player_type playerType = getPlayerType(url); - ALOGV("player type = %d", playerType); - - // create the right type of player - sp<MediaPlayerBase> p = createPlayer(playerType); - if (p == NULL) return NO_INIT; - - if (!p->hardwareOutput()) { - mAudioOutput = new AudioOutput(mAudioSessionId); - static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); + if (p == NULL) { + return NO_INIT; } - // now set data source - ALOGV(" setDataSource"); - mStatus = p->setDataSource(url, headers); - if (mStatus == NO_ERROR) { - mPlayer = p; - } else { - ALOGE(" error: %d", mStatus); - } + setDataSource_post(p, p->setDataSource(url, headers)); return mStatus; } } @@ -745,46 +831,34 @@ status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64 ALOGV("calculated length = %lld", length); } + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured for + // retransmission. player_type playerType = getPlayerType(fd, offset, length); - ALOGV("player type = %d", playerType); - - // create the right type of player - sp<MediaPlayerBase> p = createPlayer(playerType); - if (p == NULL) return NO_INIT; - - if (!p->hardwareOutput()) { - mAudioOutput = new AudioOutput(mAudioSessionId); - static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); + if (p == NULL) { + return NO_INIT; } // now set data source - mStatus = p->setDataSource(fd, offset, length); - if (mStatus == NO_ERROR) mPlayer = p; - + setDataSource_post(p, p->setDataSource(fd, offset, length)); return mStatus; } status_t MediaPlayerService::Client::setDataSource( const sp<IStreamSource> &source) { // create the right type of player - sp<MediaPlayerBase> p = createPlayer(NU_PLAYER); - + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured for + // retransmission. + player_type playerType = getPlayerType(source); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); if (p == NULL) { return NO_INIT; } - if (!p->hardwareOutput()) { - mAudioOutput = new AudioOutput(mAudioSessionId); - static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); - } - // now set data source - mStatus = p->setDataSource(source); - - if (mStatus == OK) { - mPlayer = p; - } - + setDataSource_post(p, p->setDataSource(source)); return mStatus; } @@ -1005,6 +1079,7 @@ status_t MediaPlayerService::Client::seekTo(int msec) status_t MediaPlayerService::Client::reset() { ALOGV("[%d] reset", mConnId); + mRetransmitEndpointValid = false; sp<MediaPlayerBase> p = getPlayer(); if (p == 0) return UNKNOWN_ERROR; return p->reset(); @@ -1031,9 +1106,21 @@ status_t MediaPlayerService::Client::setLooping(int loop) status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume) { ALOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume); - // TODO: for hardware output, call player instead - Mutex::Autolock l(mLock); - if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume); + + // for hardware output, call player instead + sp<MediaPlayerBase> p = getPlayer(); + { + Mutex::Autolock l(mLock); + if (p != 0 && p->hardwareOutput()) { + MediaPlayerHWInterface* hwp = + reinterpret_cast<MediaPlayerHWInterface*>(p.get()); + return hwp->setVolume(leftVolume, rightVolume); + } else { + if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume); + return NO_ERROR; + } + } + return NO_ERROR; } @@ -1067,6 +1154,36 @@ status_t MediaPlayerService::Client::getParameter(int key, Parcel *reply) { return p->getParameter(key, reply); } +status_t MediaPlayerService::Client::setRetransmitEndpoint( + const struct sockaddr_in* endpoint) { + + if (NULL != endpoint) { + uint32_t a = ntohl(endpoint->sin_addr.s_addr); + uint16_t p = ntohs(endpoint->sin_port); + ALOGV("[%d] setRetransmitEndpoint(%u.%u.%u.%u:%hu)", mConnId, + (a >> 24), (a >> 16) & 0xFF, (a >> 8) & 0xFF, (a & 0xFF), p); + } else { + ALOGV("[%d] setRetransmitEndpoint = <none>", mConnId); + } + + sp<MediaPlayerBase> p = getPlayer(); + + // Right now, the only valid time to set a retransmit endpoint is before + // player selection has been made (since the presence or absence of a + // retransmit endpoint is going to determine which player is selected during + // setDataSource). + if (p != 0) return INVALID_OPERATION; + + if (NULL != endpoint) { + mRetransmitEndpoint = *endpoint; + mRetransmitEndpointValid = true; + } else { + mRetransmitEndpointValid = false; + } + + return NO_ERROR; +} + void MediaPlayerService::Client::notify( void* cookie, int msg, int ext1, int ext2, const Parcel *obj) { @@ -1267,7 +1384,6 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId) mRightVolume = 1.0; mPlaybackRatePermille = 1000; mSampleRateHz = 0; - mLatency = 0; mMsecsPerFrame = 0; mAuxEffectId = 0; mSendLevel = 0.0; @@ -1326,7 +1442,8 @@ ssize_t MediaPlayerService::AudioOutput::frameSize() const uint32_t MediaPlayerService::AudioOutput::latency () const { - return mLatency; + if (mTrack == 0) return 0; + return mTrack->latency(); } float MediaPlayerService::AudioOutput::msecsPerFrame() const @@ -1341,7 +1458,8 @@ status_t MediaPlayerService::AudioOutput::getPosition(uint32_t *position) } status_t MediaPlayerService::AudioOutput::open( - uint32_t sampleRate, int channelCount, audio_format_t format, int bufferCount, + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount, AudioCallback cb, void *cookie) { mCallback = cb; @@ -1353,7 +1471,8 @@ status_t MediaPlayerService::AudioOutput::open( bufferCount = mMinBufferCount; } - ALOGV("open(%u, %d, %d, %d, %d)", sampleRate, channelCount, format, bufferCount,mSessionId); + ALOGV("open(%u, %d, 0x%x, %d, %d, %d)", sampleRate, channelCount, channelMask, + format, bufferCount, mSessionId); if (mTrack) close(); int afSampleRate; int afFrameCount; @@ -1368,13 +1487,21 @@ status_t MediaPlayerService::AudioOutput::open( frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; + if (channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) { + channelMask = audio_channel_mask_from_count(channelCount); + if (0 == channelMask) { + ALOGE("open() error, can\'t derive mask for %d audio channels", channelCount); + return NO_INIT; + } + } + AudioTrack *t; if (mCallback != NULL) { t = new AudioTrack( mStreamType, sampleRate, format, - (channelCount == 2) ? AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO, + channelMask, frameCount, 0 /* flags */, CallbackWrapper, @@ -1386,7 +1513,7 @@ status_t MediaPlayerService::AudioOutput::open( mStreamType, sampleRate, format, - (channelCount == 2) ? AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO, + channelMask, frameCount, 0, NULL, @@ -1406,7 +1533,6 @@ status_t MediaPlayerService::AudioOutput::open( mSampleRateHz = sampleRate; mMsecsPerFrame = mPlaybackRatePermille / (float) sampleRate; - mLatency = t->latency(); mTrack = t; status_t res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000); @@ -1634,10 +1760,11 @@ bool CallbackThread::threadLoop() { //////////////////////////////////////////////////////////////////////////////// status_t MediaPlayerService::AudioCache::open( - uint32_t sampleRate, int channelCount, audio_format_t format, int bufferCount, + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount, AudioCallback cb, void *cookie) { - ALOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount); + ALOGV("open(%u, %d, 0x%x, %d, %d)", sampleRate, channelCount, channelMask, format, bufferCount); if (mHeap->getHeapID() < 0) { return NO_INIT; } diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 52af64d..85cec22 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -18,6 +18,8 @@ #ifndef ANDROID_MEDIAPLAYERSERVICE_H #define ANDROID_MEDIAPLAYERSERVICE_H +#include <arpa/inet.h> + #include <utils/Log.h> #include <utils/threads.h> #include <utils/List.h> @@ -83,7 +85,7 @@ class MediaPlayerService : public BnMediaPlayerService virtual int getSessionId(); virtual status_t open( - uint32_t sampleRate, int channelCount, + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, audio_format_t format, int bufferCount, AudioCallback cb, void *cookie); @@ -116,7 +118,6 @@ class MediaPlayerService : public BnMediaPlayerService int32_t mPlaybackRatePermille; uint32_t mSampleRateHz; // sample rate of the content, as set in open() float mMsecsPerFrame; - uint32_t mLatency; int mSessionId; float mSendLevel; int mAuxEffectId; @@ -143,8 +144,8 @@ class MediaPlayerService : public BnMediaPlayerService virtual int getSessionId(); virtual status_t open( - uint32_t sampleRate, int channelCount, audio_format_t format, - int bufferCount = 1, + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount = 1, AudioCallback cb = NULL, void *cookie = NULL); virtual void start(); @@ -276,6 +277,7 @@ private: virtual status_t attachAuxEffect(int effectId); virtual status_t setParameter(int key, const Parcel &request); virtual status_t getParameter(int key, Parcel *reply); + virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint); sp<MediaPlayerBase> createPlayer(player_type playerType); @@ -287,6 +289,14 @@ private: virtual status_t setDataSource(const sp<IStreamSource> &source); + sp<MediaPlayerBase> setDataSource_pre(player_type playerType); + void setDataSource_post(const sp<MediaPlayerBase>& p, + status_t status); + + player_type getPlayerType(int fd, int64_t offset, int64_t length); + player_type getPlayerType(const char* url); + player_type getPlayerType(const sp<IStreamSource> &source); + static void notify(void* cookie, int msg, int ext1, int ext2, const Parcel *obj); @@ -338,6 +348,8 @@ private: uid_t mUID; sp<ANativeWindow> mConnectedWindow; sp<IBinder> mConnectedWindowBinder; + struct sockaddr_in mRetransmitEndpoint; + bool mRetransmitEndpointValid; // Metadata filters. media::Metadata::Filter mMetadataAllow; // protected by mLock diff --git a/media/libmediaplayerservice/MidiFile.cpp b/media/libmediaplayerservice/MidiFile.cpp index 7cb8c29..8db5b9b 100644 --- a/media/libmediaplayerservice/MidiFile.cpp +++ b/media/libmediaplayerservice/MidiFile.cpp @@ -421,7 +421,8 @@ status_t MidiFile::setLooping(int loop) } status_t MidiFile::createOutputTrack() { - if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) { + if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, + CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) { ALOGE("mAudioSink open failed"); return ERROR_OPEN_FAILED; } diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index fe519b0..ca79657 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -40,7 +40,7 @@ #include <media/MediaProfiles.h> #include <camera/ICamera.h> #include <camera/CameraParameters.h> -#include <surfaceflinger/Surface.h> +#include <gui/Surface.h> #include <utils/Errors.h> #include <sys/types.h> @@ -1291,6 +1291,12 @@ status_t StagefrightRecorder::setupCameraSource( videoSize.width = mVideoWidth; videoSize.height = mVideoHeight; if (mCaptureTimeLapse) { + if (mTimeBetweenTimeLapseFrameCaptureUs < 0) { + ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld", + mTimeBetweenTimeLapseFrameCaptureUs); + return BAD_VALUE; + } + mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera( mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate, mPreviewSurface, diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index b731d0f..526120a 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -38,7 +38,6 @@ #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MetaData.h> -#include <surfaceflinger/Surface.h> #include <gui/ISurfaceTexture.h> #include "avc_utils.h" @@ -337,6 +336,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { CHECK_EQ(mAudioSink->open( sampleRate, numChannels, + CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 8 /* bufferCount */), (status_t)OK); @@ -387,10 +387,10 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { audio ? "audio" : "video"); mRenderer->queueEOS(audio, UNKNOWN_ERROR); - } else { - CHECK_EQ((int)what, (int)ACodec::kWhatDrainThisBuffer); - + } else if (what == ACodec::kWhatDrainThisBuffer) { renderBuffer(audio, codecRequest); + } else { + ALOGV("Unhandled codec notification %d.", what); } break; @@ -768,7 +768,7 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { mediaTimeUs / 1E6); #endif - reply->setObject("buffer", accessUnit); + reply->setBuffer("buffer", accessUnit); reply->post(); return OK; @@ -793,10 +793,8 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) { return; } - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); int64_t &skipUntilMediaTimeUs = audio diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index ffc710e..6be14be 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -21,8 +21,6 @@ #include <media/MediaPlayerInterface.h> #include <media/stagefright/foundation/AHandler.h> #include <media/stagefright/NativeWindowWrapper.h> -#include <gui/SurfaceTextureClient.h> -#include <surfaceflinger/Surface.h> namespace android { diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp index 56c2773..460fc98 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -29,8 +29,6 @@ #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> -#include <surfaceflinger/Surface.h> -#include <gui/ISurfaceTexture.h> namespace android { @@ -214,8 +212,6 @@ sp<AMessage> NuPlayer::Decoder::makeFormat(const sp<MetaData> &meta) { buffer->meta()->setInt32("csd", true); mCSD.push(buffer); - - msg->setObject("csd", buffer); } else if (meta->findData(kKeyESDS, &type, &data, &size)) { ESDS esds((const char *)data, size); CHECK_EQ(esds.InitCheck(), (status_t)OK); @@ -242,9 +238,8 @@ void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) { CHECK(msg->findMessage("reply", &reply)); #if 0 - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - sp<ABuffer> outBuffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> outBuffer; + CHECK(msg->findBuffer("buffer", &outBuffer)); #else sp<ABuffer> outBuffer; #endif @@ -253,7 +248,7 @@ void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) { outBuffer = mCSD.editItemAt(mCSDIndex++); outBuffer->meta()->setInt64("timeUs", 0); - reply->setObject("buffer", outBuffer); + reply->setBuffer("buffer", outBuffer); reply->post(); return; } diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp index 15259cb..5738ecb 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp @@ -60,7 +60,7 @@ void NuPlayer::Renderer::queueBuffer( const sp<AMessage> ¬ifyConsumed) { sp<AMessage> msg = new AMessage(kWhatQueueBuffer, id()); msg->setInt32("audio", static_cast<int32_t>(audio)); - msg->setObject("buffer", buffer); + msg->setBuffer("buffer", buffer); msg->setMessage("notifyConsumed", notifyConsumed); msg->post(); } @@ -411,9 +411,8 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { return; } - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); sp<AMessage> notifyConsumed; CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed)); diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp index 6eb0d07..4c65b65 100644 --- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp @@ -218,10 +218,8 @@ void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) { CHECK(msg->findSize("trackIndex", &trackIndex)); CHECK_LT(trackIndex, mTracks.size()); - sp<RefBase> obj; - CHECK(msg->findObject("accessUnit", &obj)); - - sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); int32_t damaged; if (accessUnit->meta()->findInt32("damaged", &damaged) diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index ca44ea3..09e4e45 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -26,14 +26,12 @@ #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/NativeWindowWrapper.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> -#include <surfaceflinger/Surface.h> -#include <gui/SurfaceTextureClient.h> - #include <OMX_Component.h> namespace android { @@ -168,15 +166,36 @@ struct ACodec::UninitializedState : public ACodec::BaseState { protected: virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); private: void onSetup(const sp<AMessage> &msg); + bool onAllocateComponent(const sp<AMessage> &msg); DISALLOW_EVIL_CONSTRUCTORS(UninitializedState); }; //////////////////////////////////////////////////////////////////////////////// +struct ACodec::LoadedState : public ACodec::BaseState { + LoadedState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + +private: + friend struct ACodec::UninitializedState; + + bool onConfigureComponent(const sp<AMessage> &msg); + void onStart(); + void onShutdown(bool keepComponentAllocated); + + DISALLOW_EVIL_CONSTRUCTORS(LoadedState); +}; + +//////////////////////////////////////////////////////////////////////////////// + struct ACodec::LoadedToIdleState : public ACodec::BaseState { LoadedToIdleState(ACodec *codec); @@ -265,6 +284,8 @@ protected: private: void changeStateIfWeOwnAllBuffers(); + bool mComponentNowIdle; + DISALLOW_EVIL_CONSTRUCTORS(ExecutingToIdleState); }; @@ -308,9 +329,13 @@ private: //////////////////////////////////////////////////////////////////////////////// ACodec::ACodec() - : mNode(NULL), - mSentFormat(false) { + : mQuirks(0), + mNode(NULL), + mSentFormat(false), + mIsEncoder(false), + mShutdownInProgress(false) { mUninitializedState = new UninitializedState(this); + mLoadedState = new LoadedState(this); mLoadedToIdleState = new LoadedToIdleState(this); mIdleToExecutingState = new IdleToExecutingState(this); mExecutingState = new ExecutingState(this); @@ -341,6 +366,22 @@ void ACodec::initiateSetup(const sp<AMessage> &msg) { msg->post(); } +void ACodec::initiateAllocateComponent(const sp<AMessage> &msg) { + msg->setWhat(kWhatAllocateComponent); + msg->setTarget(id()); + msg->post(); +} + +void ACodec::initiateConfigureComponent(const sp<AMessage> &msg) { + msg->setWhat(kWhatConfigureComponent); + msg->setTarget(id()); + msg->post(); +} + +void ACodec::initiateStart() { + (new AMessage(kWhatStart, id()))->post(); +} + void ACodec::signalFlush() { ALOGV("[%s] signalFlush", mComponentName.c_str()); (new AMessage(kWhatFlush, id()))->post(); @@ -350,8 +391,10 @@ void ACodec::signalResume() { (new AMessage(kWhatResume, id()))->post(); } -void ACodec::initiateShutdown() { - (new AMessage(kWhatShutdown, id()))->post(); +void ACodec::initiateShutdown(bool keepComponentAllocated) { + sp<AMessage> msg = new AMessage(kWhatShutdown, id()); + msg->setInt32("keepComponentAllocated", keepComponentAllocated); + msg->post(); } status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { @@ -360,62 +403,71 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { CHECK(mDealer[portIndex] == NULL); CHECK(mBuffers[portIndex].isEmpty()); + status_t err; if (mNativeWindow != NULL && portIndex == kPortIndexOutput) { - return allocateOutputBuffersFromNativeWindow(); - } - - OMX_PARAM_PORTDEFINITIONTYPE def; - InitOMXParams(&def); - def.nPortIndex = portIndex; + err = allocateOutputBuffersFromNativeWindow(); + } else { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = portIndex; - status_t err = mOMX->getParameter( - mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - if (err != OK) { - return err; - } + if (err == OK) { + ALOGV("[%s] Allocating %lu buffers of size %lu on %s port", + mComponentName.c_str(), + def.nBufferCountActual, def.nBufferSize, + portIndex == kPortIndexInput ? "input" : "output"); - ALOGV("[%s] Allocating %lu buffers of size %lu on %s port", - mComponentName.c_str(), - def.nBufferCountActual, def.nBufferSize, - portIndex == kPortIndexInput ? "input" : "output"); + size_t totalSize = def.nBufferCountActual * def.nBufferSize; + mDealer[portIndex] = new MemoryDealer(totalSize, "ACodec"); - size_t totalSize = def.nBufferCountActual * def.nBufferSize; - mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec"); + for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) { + sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize); + CHECK(mem.get() != NULL); - for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) { - sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize); - CHECK(mem.get() != NULL); + IOMX::buffer_id buffer; - IOMX::buffer_id buffer; + uint32_t requiresAllocateBufferBit = + (portIndex == kPortIndexInput) + ? OMXCodec::kRequiresAllocateBufferOnInputPorts + : OMXCodec::kRequiresAllocateBufferOnOutputPorts; - if (!strcasecmp( - mComponentName.c_str(), "OMX.TI.DUCATI1.VIDEO.DECODER")) { - if (portIndex == kPortIndexInput && i == 0) { - // Only log this warning once per allocation round. + if (mQuirks & requiresAllocateBufferBit) { + err = mOMX->allocateBufferWithBackup( + mNode, portIndex, mem, &buffer); + } else { + err = mOMX->useBuffer(mNode, portIndex, mem, &buffer); + } - ALOGW("OMX.TI.DUCATI1.VIDEO.DECODER requires the use of " - "OMX_AllocateBuffer instead of the preferred " - "OMX_UseBuffer. Vendor must fix this."); + BufferInfo info; + info.mBufferID = buffer; + info.mStatus = BufferInfo::OWNED_BY_US; + info.mData = new ABuffer(mem->pointer(), def.nBufferSize); + mBuffers[portIndex].push(info); } - - err = mOMX->allocateBufferWithBackup( - mNode, portIndex, mem, &buffer); - } else { - err = mOMX->useBuffer(mNode, portIndex, mem, &buffer); } + } - if (err != OK) { - return err; - } + if (err != OK) { + return err; + } - BufferInfo info; - info.mBufferID = buffer; - info.mStatus = BufferInfo::OWNED_BY_US; - info.mData = new ABuffer(mem->pointer(), def.nBufferSize); - mBuffers[portIndex].push(info); + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", ACodec::kWhatBuffersAllocated); + + notify->setInt32("portIndex", portIndex); + for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) { + AString name = StringPrintf("buffer-id_%d", i); + notify->setPointer(name.c_str(), mBuffers[portIndex][i].mBufferID); + + name = StringPrintf("data_%d", i); + notify->setBuffer(name.c_str(), mBuffers[portIndex][i].mData); } + notify->post(); + return OK; } @@ -671,7 +723,7 @@ ACodec::BufferInfo *ACodec::findBufferByID( return NULL; } -void ACodec::setComponentRole( +status_t ACodec::setComponentRole( bool isEncoder, const char *mime) { struct MimeToRole { const char *mime; @@ -700,6 +752,8 @@ void ACodec::setComponentRole( "video_decoder.mpeg4", "video_encoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_H263, "video_decoder.h263", "video_encoder.h263" }, + { MEDIA_MIMETYPE_VIDEO_VPX, + "video_decoder.vpx", "video_encoder.vpx" }, }; static const size_t kNumMimeToRole = @@ -713,7 +767,7 @@ void ACodec::setComponentRole( } if (i == kNumMimeToRole) { - return; + return ERROR_UNSUPPORTED; } const char *role = @@ -736,50 +790,83 @@ void ACodec::setComponentRole( if (err != OK) { ALOGW("[%s] Failed to set standard component role '%s'.", mComponentName.c_str(), role); + + return err; } } + + return OK; } -void ACodec::configureCodec( +status_t ACodec::configureCodec( const char *mime, const sp<AMessage> &msg) { - setComponentRole(false /* isEncoder */, mime); + int32_t encoder; + if (!msg->findInt32("encoder", &encoder)) { + encoder = false; + } - if (!strncasecmp(mime, "video/", 6)) { - int32_t width, height; - CHECK(msg->findInt32("width", &width)); - CHECK(msg->findInt32("height", &height)); + mIsEncoder = encoder; - CHECK_EQ(setupVideoDecoder(mime, width, height), - (status_t)OK); + status_t err = setComponentRole(encoder /* isEncoder */, mime); + + if (err != OK) { + return err; + } + + int32_t bitRate = 0; + if (encoder && !msg->findInt32("bitrate", &bitRate)) { + return INVALID_OPERATION; + } + + if (!strncasecmp(mime, "video/", 6)) { + if (encoder) { + err = setupVideoEncoder(mime, msg); + } else { + int32_t width, height; + if (!msg->findInt32("width", &width) + || !msg->findInt32("height", &height)) { + err = INVALID_OPERATION; + } else { + err = setupVideoDecoder(mime, width, height); + } + } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { int32_t numChannels, sampleRate; - CHECK(msg->findInt32("channel-count", &numChannels)); - CHECK(msg->findInt32("sample-rate", &sampleRate)); - - CHECK_EQ(setupAACDecoder(numChannels, sampleRate), (status_t)OK); + if (!msg->findInt32("channel-count", &numChannels) + || !msg->findInt32("sample-rate", &sampleRate)) { + err = INVALID_OPERATION; + } else { + err = setupAACCodec(encoder, numChannels, sampleRate, bitRate); + } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) { - CHECK_EQ(setupAMRDecoder(false /* isWAMR */), (status_t)OK); + err = setupAMRCodec(encoder, false /* isWAMR */, bitRate); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) { - CHECK_EQ(setupAMRDecoder(true /* isWAMR */), (status_t)OK); + err = setupAMRCodec(encoder, true /* isWAMR */, bitRate); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW) || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) { // These are PCM-like formats with a fixed sample rate but // a variable number of channels. int32_t numChannels; - CHECK(msg->findInt32("channel-count", &numChannels)); + if (!msg->findInt32("channel-count", &numChannels)) { + err = INVALID_OPERATION; + } else { + err = setupG711Codec(encoder, numChannels); + } + } - CHECK_EQ(setupG711Decoder(numChannels), (status_t)OK); + if (err != OK) { + return err; } int32_t maxInputSize; if (msg->findInt32("max-input-size", &maxInputSize)) { - CHECK_EQ(setMinBufferSize(kPortIndexInput, (size_t)maxInputSize), - (status_t)OK); + err = setMinBufferSize(kPortIndexInput, (size_t)maxInputSize); } else if (!strcmp("OMX.Nvidia.aac.decoder", mComponentName.c_str())) { - CHECK_EQ(setMinBufferSize(kPortIndexInput, 8192), // XXX - (status_t)OK); + err = setMinBufferSize(kPortIndexInput, 8192); // XXX } + + return err; } status_t ACodec::setMinBufferSize(OMX_U32 portIndex, size_t size) { @@ -819,12 +906,113 @@ status_t ACodec::setMinBufferSize(OMX_U32 portIndex, size_t size) { return OK; } -status_t ACodec::setupAACDecoder(int32_t numChannels, int32_t sampleRate) { +status_t ACodec::selectAudioPortFormat( + OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat) { + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + InitOMXParams(&format); + + format.nPortIndex = portIndex; + for (OMX_U32 index = 0;; ++index) { + format.nIndex = index; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)); + + if (err != OK) { + return err; + } + + if (format.eEncoding == desiredFormat) { + break; + } + } + + return mOMX->setParameter( + mNode, OMX_IndexParamAudioPortFormat, &format, sizeof(format)); +} + +status_t ACodec::setupAACCodec( + bool encoder, + int32_t numChannels, int32_t sampleRate, int32_t bitRate) { + status_t err = setupRawAudioFormat( + encoder ? kPortIndexInput : kPortIndexOutput, + sampleRate, + numChannels); + + if (err != OK) { + return err; + } + + if (encoder) { + err = selectAudioPortFormat(kPortIndexOutput, OMX_AUDIO_CodingAAC); + + if (err != OK) { + return err; + } + + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + def.format.audio.bFlagErrorConcealment = OMX_TRUE; + def.format.audio.eEncoding = OMX_AUDIO_CodingAAC; + + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + OMX_AUDIO_PARAM_AACPROFILETYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + + if (err != OK) { + return err; + } + + profile.nChannels = numChannels; + + profile.eChannelMode = + (numChannels == 1) + ? OMX_AUDIO_ChannelModeMono: OMX_AUDIO_ChannelModeStereo; + + profile.nSampleRate = sampleRate; + profile.nBitRate = bitRate; + profile.nAudioBandWidth = 0; + profile.nFrameLength = 0; + profile.nAACtools = OMX_AUDIO_AACToolAll; + profile.nAACERtools = OMX_AUDIO_AACERNone; + profile.eAACProfile = OMX_AUDIO_AACObjectLC; + profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF; + + err = mOMX->setParameter( + mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + + if (err != OK) { + return err; + } + + return err; + } + OMX_AUDIO_PARAM_AACPROFILETYPE profile; InitOMXParams(&profile); profile.nPortIndex = kPortIndexInput; - status_t err = mOMX->getParameter( + err = mOMX->getParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); if (err != OK) { @@ -835,16 +1023,59 @@ status_t ACodec::setupAACDecoder(int32_t numChannels, int32_t sampleRate) { profile.nSampleRate = sampleRate; profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; - err = mOMX->setParameter( + return mOMX->setParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); +} - return err; +static OMX_AUDIO_AMRBANDMODETYPE pickModeFromBitRate( + bool isAMRWB, int32_t bps) { + if (isAMRWB) { + if (bps <= 6600) { + return OMX_AUDIO_AMRBandModeWB0; + } else if (bps <= 8850) { + return OMX_AUDIO_AMRBandModeWB1; + } else if (bps <= 12650) { + return OMX_AUDIO_AMRBandModeWB2; + } else if (bps <= 14250) { + return OMX_AUDIO_AMRBandModeWB3; + } else if (bps <= 15850) { + return OMX_AUDIO_AMRBandModeWB4; + } else if (bps <= 18250) { + return OMX_AUDIO_AMRBandModeWB5; + } else if (bps <= 19850) { + return OMX_AUDIO_AMRBandModeWB6; + } else if (bps <= 23050) { + return OMX_AUDIO_AMRBandModeWB7; + } + + // 23850 bps + return OMX_AUDIO_AMRBandModeWB8; + } else { // AMRNB + if (bps <= 4750) { + return OMX_AUDIO_AMRBandModeNB0; + } else if (bps <= 5150) { + return OMX_AUDIO_AMRBandModeNB1; + } else if (bps <= 5900) { + return OMX_AUDIO_AMRBandModeNB2; + } else if (bps <= 6700) { + return OMX_AUDIO_AMRBandModeNB3; + } else if (bps <= 7400) { + return OMX_AUDIO_AMRBandModeNB4; + } else if (bps <= 7950) { + return OMX_AUDIO_AMRBandModeNB5; + } else if (bps <= 10200) { + return OMX_AUDIO_AMRBandModeNB6; + } + + // 12200 bps + return OMX_AUDIO_AMRBandModeNB7; + } } -status_t ACodec::setupAMRDecoder(bool isWAMR) { +status_t ACodec::setupAMRCodec(bool encoder, bool isWAMR, int32_t bitrate) { OMX_AUDIO_PARAM_AMRTYPE def; InitOMXParams(&def); - def.nPortIndex = kPortIndexInput; + def.nPortIndex = encoder ? kPortIndexOutput : kPortIndexInput; status_t err = mOMX->getParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); @@ -854,14 +1085,24 @@ status_t ACodec::setupAMRDecoder(bool isWAMR) { } def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + def.eAMRBandMode = pickModeFromBitRate(isWAMR, bitrate); + + err = mOMX->setParameter( + mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); - def.eAMRBandMode = - isWAMR ? OMX_AUDIO_AMRBandModeWB0 : OMX_AUDIO_AMRBandModeNB0; + if (err != OK) { + return err; + } - return mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + return setupRawAudioFormat( + encoder ? kPortIndexInput : kPortIndexOutput, + isWAMR ? 16000 : 8000 /* sampleRate */, + 1 /* numChannels */); } -status_t ACodec::setupG711Decoder(int32_t numChannels) { +status_t ACodec::setupG711Codec(bool encoder, int32_t numChannels) { + CHECK(!encoder); // XXX TODO + return setupRawAudioFormat( kPortIndexInput, 8000 /* sampleRate */, numChannels); } @@ -1001,22 +1242,36 @@ status_t ACodec::setSupportedOutputFormat() { &format, sizeof(format)); } -status_t ACodec::setupVideoDecoder( - const char *mime, int32_t width, int32_t height) { - OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused; +static status_t GetVideoCodingTypeFromMime( + const char *mime, OMX_VIDEO_CODINGTYPE *codingType) { if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { - compressionFormat = OMX_VIDEO_CodingAVC; + *codingType = OMX_VIDEO_CodingAVC; } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { - compressionFormat = OMX_VIDEO_CodingMPEG4; + *codingType = OMX_VIDEO_CodingMPEG4; } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { - compressionFormat = OMX_VIDEO_CodingH263; + *codingType = OMX_VIDEO_CodingH263; } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) { - compressionFormat = OMX_VIDEO_CodingMPEG2; + *codingType = OMX_VIDEO_CodingMPEG2; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VPX, mime)) { + *codingType = OMX_VIDEO_CodingVPX; } else { - TRESPASS(); + *codingType = OMX_VIDEO_CodingUnused; + return ERROR_UNSUPPORTED; } - status_t err = setVideoPortFormatType( + return OK; +} + +status_t ACodec::setupVideoDecoder( + const char *mime, int32_t width, int32_t height) { + OMX_VIDEO_CODINGTYPE compressionFormat; + status_t err = GetVideoCodingTypeFromMime(mime, &compressionFormat); + + if (err != OK) { + return err; + } + + err = setVideoPortFormatType( kPortIndexInput, compressionFormat, OMX_COLOR_FormatUnused); if (err != OK) { @@ -1046,6 +1301,489 @@ status_t ACodec::setupVideoDecoder( return OK; } +status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) { + int32_t tmp; + if (!msg->findInt32("color-format", &tmp)) { + return INVALID_OPERATION; + } + + OMX_COLOR_FORMATTYPE colorFormat = + static_cast<OMX_COLOR_FORMATTYPE>(tmp); + + status_t err = setVideoPortFormatType( + kPortIndexInput, OMX_VIDEO_CodingUnused, colorFormat); + + if (err != OK) { + ALOGE("[%s] does not support color format %d", + mComponentName.c_str(), colorFormat); + + return err; + } + + /* Input port configuration */ + + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + def.nPortIndex = kPortIndexInput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + int32_t width, height, bitrate; + if (!msg->findInt32("width", &width) + || !msg->findInt32("height", &height) + || !msg->findInt32("bitrate", &bitrate)) { + return INVALID_OPERATION; + } + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + + int32_t stride; + if (!msg->findInt32("stride", &stride)) { + stride = width; + } + + video_def->nStride = stride; + + int32_t sliceHeight; + if (!msg->findInt32("slice-height", &sliceHeight)) { + sliceHeight = height; + } + + video_def->nSliceHeight = sliceHeight; + + def.nBufferSize = (video_def->nStride * video_def->nSliceHeight * 3) / 2; + + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f); + video_def->eCompressionFormat = OMX_VIDEO_CodingUnused; + video_def->eColorFormat = colorFormat; + + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + ALOGE("[%s] failed to set input port definition parameters.", + mComponentName.c_str()); + + return err; + } + + /* Output port configuration */ + + OMX_VIDEO_CODINGTYPE compressionFormat; + err = GetVideoCodingTypeFromMime(mime, &compressionFormat); + + if (err != OK) { + return err; + } + + err = setVideoPortFormatType( + kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused); + + if (err != OK) { + ALOGE("[%s] does not support compression format %d", + mComponentName.c_str(), compressionFormat); + + return err; + } + + def.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + video_def->xFramerate = 0; + video_def->nBitrate = bitrate; + video_def->eCompressionFormat = compressionFormat; + video_def->eColorFormat = OMX_COLOR_FormatUnused; + + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + ALOGE("[%s] failed to set output port definition parameters.", + mComponentName.c_str()); + + return err; + } + + switch (compressionFormat) { + case OMX_VIDEO_CodingMPEG4: + err = setupMPEG4EncoderParameters(msg); + break; + + case OMX_VIDEO_CodingH263: + err = setupH263EncoderParameters(msg); + break; + + case OMX_VIDEO_CodingAVC: + err = setupAVCEncoderParameters(msg); + break; + + default: + break; + } + + ALOGI("setupVideoEncoder succeeded"); + + return err; +} + +static OMX_U32 setPFramesSpacing(int32_t iFramesInterval, int32_t frameRate) { + if (iFramesInterval < 0) { + return 0xFFFFFFFF; + } else if (iFramesInterval == 0) { + return 0; + } + OMX_U32 ret = frameRate * iFramesInterval; + CHECK(ret > 1); + return ret; +} + +status_t ACodec::setupMPEG4EncoderParameters(const sp<AMessage> &msg) { + int32_t bitrate, iFrameInterval; + if (!msg->findInt32("bitrate", &bitrate) + || !msg->findInt32("i-frame-interval", &iFrameInterval)) { + return INVALID_OPERATION; + } + + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type; + InitOMXParams(&mpeg4type); + mpeg4type.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); + + if (err != OK) { + return err; + } + + mpeg4type.nSliceHeaderSpacing = 0; + mpeg4type.bSVH = OMX_FALSE; + mpeg4type.bGov = OMX_FALSE; + + mpeg4type.nAllowedPictureTypes = + OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; + + mpeg4type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate); + if (mpeg4type.nPFrames == 0) { + mpeg4type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI; + } + mpeg4type.nBFrames = 0; + mpeg4type.nIDCVLCThreshold = 0; + mpeg4type.bACPred = OMX_TRUE; + mpeg4type.nMaxPacketSize = 256; + mpeg4type.nTimeIncRes = 1000; + mpeg4type.nHeaderExtension = 0; + mpeg4type.bReversibleVLC = OMX_FALSE; + + int32_t profile; + if (msg->findInt32("profile", &profile)) { + int32_t level; + if (!msg->findInt32("level", &level)) { + return INVALID_OPERATION; + } + + err = verifySupportForProfileAndLevel(profile, level); + + if (err != OK) { + return err; + } + + mpeg4type.eProfile = static_cast<OMX_VIDEO_MPEG4PROFILETYPE>(profile); + mpeg4type.eLevel = static_cast<OMX_VIDEO_MPEG4LEVELTYPE>(level); + } + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); + + if (err != OK) { + return err; + } + + err = configureBitrate(bitrate); + + if (err != OK) { + return err; + } + + return setupErrorCorrectionParameters(); +} + +status_t ACodec::setupH263EncoderParameters(const sp<AMessage> &msg) { + int32_t bitrate, iFrameInterval; + if (!msg->findInt32("bitrate", &bitrate) + || !msg->findInt32("i-frame-interval", &iFrameInterval)) { + return INVALID_OPERATION; + } + + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + OMX_VIDEO_PARAM_H263TYPE h263type; + InitOMXParams(&h263type); + h263type.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type)); + + if (err != OK) { + return err; + } + + h263type.nAllowedPictureTypes = + OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; + + h263type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate); + if (h263type.nPFrames == 0) { + h263type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI; + } + h263type.nBFrames = 0; + + int32_t profile; + if (msg->findInt32("profile", &profile)) { + int32_t level; + if (!msg->findInt32("level", &level)) { + return INVALID_OPERATION; + } + + err = verifySupportForProfileAndLevel(profile, level); + + if (err != OK) { + return err; + } + + h263type.eProfile = static_cast<OMX_VIDEO_H263PROFILETYPE>(profile); + h263type.eLevel = static_cast<OMX_VIDEO_H263LEVELTYPE>(level); + } + + h263type.bPLUSPTYPEAllowed = OMX_FALSE; + h263type.bForceRoundingTypeToZero = OMX_FALSE; + h263type.nPictureHeaderRepetition = 0; + h263type.nGOBHeaderInterval = 0; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type)); + + if (err != OK) { + return err; + } + + err = configureBitrate(bitrate); + + if (err != OK) { + return err; + } + + return setupErrorCorrectionParameters(); +} + +status_t ACodec::setupAVCEncoderParameters(const sp<AMessage> &msg) { + int32_t bitrate, iFrameInterval; + if (!msg->findInt32("bitrate", &bitrate) + || !msg->findInt32("i-frame-interval", &iFrameInterval)) { + return INVALID_OPERATION; + } + + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + OMX_VIDEO_PARAM_AVCTYPE h264type; + InitOMXParams(&h264type); + h264type.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); + + if (err != OK) { + return err; + } + + h264type.nAllowedPictureTypes = + OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; + + int32_t profile; + if (msg->findInt32("profile", &profile)) { + int32_t level; + if (!msg->findInt32("level", &level)) { + return INVALID_OPERATION; + } + + err = verifySupportForProfileAndLevel(profile, level); + + if (err != OK) { + return err; + } + + h264type.eProfile = static_cast<OMX_VIDEO_AVCPROFILETYPE>(profile); + h264type.eLevel = static_cast<OMX_VIDEO_AVCLEVELTYPE>(level); + } + + // XXX + if (!strncmp(mComponentName.c_str(), "OMX.TI.DUCATI1", 14)) { + h264type.eProfile = OMX_VIDEO_AVCProfileBaseline; + } + + if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) { + h264type.nSliceHeaderSpacing = 0; + h264type.bUseHadamard = OMX_TRUE; + h264type.nRefFrames = 1; + h264type.nBFrames = 0; + h264type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate); + if (h264type.nPFrames == 0) { + h264type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI; + } + h264type.nRefIdx10ActiveMinus1 = 0; + h264type.nRefIdx11ActiveMinus1 = 0; + h264type.bEntropyCodingCABAC = OMX_FALSE; + h264type.bWeightedPPrediction = OMX_FALSE; + h264type.bconstIpred = OMX_FALSE; + h264type.bDirect8x8Inference = OMX_FALSE; + h264type.bDirectSpatialTemporal = OMX_FALSE; + h264type.nCabacInitIdc = 0; + } + + if (h264type.nBFrames != 0) { + h264type.nAllowedPictureTypes |= OMX_VIDEO_PictureTypeB; + } + + h264type.bEnableUEP = OMX_FALSE; + h264type.bEnableFMO = OMX_FALSE; + h264type.bEnableASO = OMX_FALSE; + h264type.bEnableRS = OMX_FALSE; + h264type.bFrameMBsOnly = OMX_TRUE; + h264type.bMBAFF = OMX_FALSE; + h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable; + + if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName.c_str())) { + h264type.eLevel = OMX_VIDEO_AVCLevelMax; + } + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); + + if (err != OK) { + return err; + } + + return configureBitrate(bitrate); +} + +status_t ACodec::verifySupportForProfileAndLevel( + int32_t profile, int32_t level) { + OMX_VIDEO_PARAM_PROFILELEVELTYPE params; + InitOMXParams(¶ms); + params.nPortIndex = kPortIndexOutput; + + for (params.nProfileIndex = 0;; ++params.nProfileIndex) { + status_t err = mOMX->getParameter( + mNode, + OMX_IndexParamVideoProfileLevelQuerySupported, + ¶ms, + sizeof(params)); + + if (err != OK) { + return err; + } + + int32_t supportedProfile = static_cast<int32_t>(params.eProfile); + int32_t supportedLevel = static_cast<int32_t>(params.eLevel); + + if (profile == supportedProfile && level <= supportedLevel) { + return OK; + } + } +} + +status_t ACodec::configureBitrate(int32_t bitrate) { + OMX_VIDEO_PARAM_BITRATETYPE bitrateType; + InitOMXParams(&bitrateType); + bitrateType.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); + + if (err != OK) { + return err; + } + + bitrateType.eControlRate = OMX_Video_ControlRateVariable; + bitrateType.nTargetBitrate = bitrate; + + return mOMX->setParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); +} + +status_t ACodec::setupErrorCorrectionParameters() { + OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType; + InitOMXParams(&errorCorrectionType); + errorCorrectionType.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoErrorCorrection, + &errorCorrectionType, sizeof(errorCorrectionType)); + + if (err != OK) { + return OK; // Optional feature. Ignore this failure + } + + errorCorrectionType.bEnableHEC = OMX_FALSE; + errorCorrectionType.bEnableResync = OMX_TRUE; + errorCorrectionType.nResynchMarkerSpacing = 256; + errorCorrectionType.bEnableDataPartitioning = OMX_FALSE; + errorCorrectionType.bEnableRVLC = OMX_FALSE; + + return mOMX->setParameter( + mNode, OMX_IndexParamVideoErrorCorrection, + &errorCorrectionType, sizeof(errorCorrectionType)); +} + status_t ACodec::setVideoFormatOnPort( OMX_U32 portIndex, int32_t width, int32_t height, OMX_VIDEO_CODINGTYPE compressionFormat) { @@ -1166,6 +1904,9 @@ void ACodec::sendFormatChange() { notify->setString("mime", MEDIA_MIMETYPE_VIDEO_RAW); notify->setInt32("width", videoDef->nFrameWidth); notify->setInt32("height", videoDef->nFrameHeight); + notify->setInt32("stride", videoDef->nStride); + notify->setInt32("slice-height", videoDef->nSliceHeight); + notify->setInt32("color-format", videoDef->eColorFormat); OMX_CONFIG_RECTTYPE rect; InitOMXParams(&rect); @@ -1241,10 +1982,11 @@ void ACodec::sendFormatChange() { mSentFormat = true; } -void ACodec::signalError(OMX_ERRORTYPE error) { +void ACodec::signalError(OMX_ERRORTYPE error, status_t internalError) { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", ACodec::kWhatError); notify->setInt32("omx-error", error); + notify->setInt32("err", internalError); notify->post(); } @@ -1417,7 +2159,7 @@ void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) { notify->setPointer("buffer-id", info->mBufferID); info->mData->meta()->clear(); - notify->setObject("buffer", info->mData); + notify->setBuffer("buffer", info->mData); sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, mCodec->id()); reply->setPointer("buffer-id", info->mBufferID); @@ -1433,18 +2175,26 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { IOMX::buffer_id bufferID; CHECK(msg->findPointer("buffer-id", &bufferID)); - sp<RefBase> obj; + sp<ABuffer> buffer; int32_t err = OK; - if (!msg->findObject("buffer", &obj)) { + bool eos = false; + + if (!msg->findBuffer("buffer", &buffer)) { CHECK(msg->findInt32("err", &err)); ALOGV("[%s] saw error %d instead of an input buffer", mCodec->mComponentName.c_str(), err); - obj.clear(); + buffer.clear(); + + eos = true; } - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + int32_t tmp; + if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) { + eos = true; + err = ERROR_END_OF_STREAM; + } BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID); CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_UPSTREAM); @@ -1456,7 +2206,7 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { switch (mode) { case KEEP_BUFFERS: { - if (buffer == NULL) { + if (eos) { if (!mCodec->mPortEOS[kPortIndexInput]) { mCodec->mPortEOS[kPortIndexInput] = true; mCodec->mInputEOSResult = err; @@ -1467,9 +2217,7 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { case RESUBMIT_BUFFERS: { - if (buffer != NULL) { - CHECK(!mCodec->mPortEOS[kPortIndexInput]); - + if (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) { int64_t timeUs; CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); @@ -1480,6 +2228,10 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { flags |= OMX_BUFFERFLAG_CODECCONFIG; } + if (eos) { + flags |= OMX_BUFFERFLAG_EOS; + } + if (buffer != info->mData) { if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) { ALOGV("[%s] Needs to copy input data.", @@ -1493,6 +2245,9 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { if (flags & OMX_BUFFERFLAG_CODECCONFIG) { ALOGV("[%s] calling emptyBuffer %p w/ codec specific data", mCodec->mComponentName.c_str(), bufferID); + } else if (flags & OMX_BUFFERFLAG_EOS) { + ALOGV("[%s] calling emptyBuffer %p w/ EOS", + mCodec->mComponentName.c_str(), bufferID); } else { ALOGV("[%s] calling emptyBuffer %p w/ time %lld us", mCodec->mComponentName.c_str(), bufferID, timeUs); @@ -1509,7 +2264,15 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { info->mStatus = BufferInfo::OWNED_BY_COMPONENT; - getMoreInputDataIfPossible(); + if (!eos) { + getMoreInputDataIfPossible(); + } else { + ALOGV("[%s] Signalled EOS on the input port", + mCodec->mComponentName.c_str()); + + mCodec->mPortEOS[kPortIndexInput] = true; + mCodec->mInputEOSResult = err; + } } else if (!mCodec->mPortEOS[kPortIndexInput]) { if (err != ERROR_END_OF_STREAM) { ALOGV("[%s] Signalling EOS on the input port " @@ -1582,8 +2345,8 @@ bool ACodec::BaseState::onOMXFillBufferDone( int64_t timeUs, void *platformPrivate, void *dataPtr) { - ALOGV("[%s] onOMXFillBufferDone %p time %lld us", - mCodec->mComponentName.c_str(), bufferID, timeUs); + ALOGV("[%s] onOMXFillBufferDone %p time %lld us, flags = 0x%08lx", + mCodec->mComponentName.c_str(), bufferID, timeUs, flags); ssize_t index; BufferInfo *info = @@ -1601,46 +2364,48 @@ bool ACodec::BaseState::onOMXFillBufferDone( case RESUBMIT_BUFFERS: { - if (rangeLength == 0) { - if (!(flags & OMX_BUFFERFLAG_EOS)) { - ALOGV("[%s] calling fillBuffer %p", - mCodec->mComponentName.c_str(), info->mBufferID); + if (rangeLength == 0 && !(flags & OMX_BUFFERFLAG_EOS)) { + ALOGV("[%s] calling fillBuffer %p", + mCodec->mComponentName.c_str(), info->mBufferID); - CHECK_EQ(mCodec->mOMX->fillBuffer( - mCodec->mNode, info->mBufferID), - (status_t)OK); + CHECK_EQ(mCodec->mOMX->fillBuffer( + mCodec->mNode, info->mBufferID), + (status_t)OK); - info->mStatus = BufferInfo::OWNED_BY_COMPONENT; - } - } else { - if (!mCodec->mSentFormat) { - mCodec->sendFormatChange(); - } + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + break; + } - if (mCodec->mNativeWindow == NULL) { - info->mData->setRange(rangeOffset, rangeLength); - } + if (!mCodec->mIsEncoder && !mCodec->mSentFormat) { + mCodec->sendFormatChange(); + } - info->mData->meta()->setInt64("timeUs", timeUs); + if (mCodec->mNativeWindow == NULL) { + info->mData->setRange(rangeOffset, rangeLength); + } - sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatDrainThisBuffer); - notify->setPointer("buffer-id", info->mBufferID); - notify->setObject("buffer", info->mData); + info->mData->meta()->setInt64("timeUs", timeUs); - sp<AMessage> reply = - new AMessage(kWhatOutputBufferDrained, mCodec->id()); + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatDrainThisBuffer); + notify->setPointer("buffer-id", info->mBufferID); + notify->setBuffer("buffer", info->mData); + notify->setInt32("flags", flags); - reply->setPointer("buffer-id", info->mBufferID); + sp<AMessage> reply = + new AMessage(kWhatOutputBufferDrained, mCodec->id()); - notify->setMessage("reply", reply); + reply->setPointer("buffer-id", info->mBufferID); - notify->post(); + notify->setMessage("reply", reply); - info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM; - } + notify->post(); + + info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM; if (flags & OMX_BUFFERFLAG_EOS) { + ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str()); + sp<AMessage> notify = mCodec->mNotify->dup(); notify->setInt32("what", ACodec::kWhatEOS); notify->setInt32("err", mCodec->mInputEOSResult); @@ -1678,12 +2443,13 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) { && msg->findInt32("render", &render) && render != 0) { // The client wants this buffer to be rendered. - if (mCodec->mNativeWindow->queueBuffer( + status_t err; + if ((err = mCodec->mNativeWindow->queueBuffer( mCodec->mNativeWindow.get(), - info->mGraphicBuffer.get()) == OK) { + info->mGraphicBuffer.get())) == OK) { info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; } else { - mCodec->signalError(); + mCodec->signalError(OMX_ErrorUndefined, err); info->mStatus = BufferInfo::OWNED_BY_US; } } else { @@ -1746,6 +2512,10 @@ ACodec::UninitializedState::UninitializedState(ACodec *codec) : BaseState(codec) { } +void ACodec::UninitializedState::stateEntered() { + ALOGV("Now uninitialized"); +} + bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { bool handled = false; @@ -1758,8 +2528,20 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { break; } + case ACodec::kWhatAllocateComponent: + { + onAllocateComponent(msg); + handled = true; + break; + } + case ACodec::kWhatShutdown: { + int32_t keepComponentAllocated; + CHECK(msg->findInt32( + "keepComponentAllocated", &keepComponentAllocated)); + CHECK(!keepComponentAllocated); + sp<AMessage> notify = mCodec->mNotify->dup(); notify->setInt32("what", ACodec::kWhatShutdownCompleted); notify->post(); @@ -1787,30 +2569,60 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { void ACodec::UninitializedState::onSetup( const sp<AMessage> &msg) { + if (onAllocateComponent(msg) + && mCodec->mLoadedState->onConfigureComponent(msg)) { + mCodec->mLoadedState->onStart(); + } +} + +bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { + ALOGV("onAllocateComponent"); + + CHECK(mCodec->mNode == NULL); + OMXClient client; CHECK_EQ(client.connect(), (status_t)OK); sp<IOMX> omx = client.interface(); + Vector<String8> matchingCodecs; + Vector<uint32_t> matchingCodecQuirks; + AString mime; - CHECK(msg->findString("mime", &mime)); - Vector<String8> matchingCodecs; - OMXCodec::findMatchingCodecs( - mime.c_str(), - false, // createEncoder - NULL, // matchComponentName - 0, // flags - &matchingCodecs); + AString componentName; + uint32_t quirks; + if (msg->findString("componentName", &componentName)) { + matchingCodecs.push_back(String8(componentName.c_str())); + + if (!OMXCodec::findCodecQuirks(componentName.c_str(), &quirks)) { + quirks = 0; + } + matchingCodecQuirks.push_back(quirks); + } else { + CHECK(msg->findString("mime", &mime)); + + int32_t encoder; + if (!msg->findInt32("encoder", &encoder)) { + encoder = false; + } + + OMXCodec::findMatchingCodecs( + mime.c_str(), + encoder, // createEncoder + NULL, // matchComponentName + 0, // flags + &matchingCodecs, + &matchingCodecQuirks); + } sp<CodecObserver> observer = new CodecObserver; IOMX::node_id node = NULL; - AString componentName; - for (size_t matchIndex = 0; matchIndex < matchingCodecs.size(); ++matchIndex) { componentName = matchingCodecs.itemAt(matchIndex).string(); + quirks = matchingCodecQuirks.itemAt(matchIndex); pid_t tid = androidGetTid(); int prevPriority = androidGetThreadPriority(tid); @@ -1826,16 +2638,22 @@ void ACodec::UninitializedState::onSetup( } if (node == NULL) { - ALOGE("Unable to instantiate a decoder for type '%s'.", mime.c_str()); + if (!mime.empty()) { + ALOGE("Unable to instantiate a decoder for type '%s'.", + mime.c_str()); + } else { + ALOGE("Unable to instantiate decoder '%s'.", componentName.c_str()); + } mCodec->signalError(OMX_ErrorComponentNotFound); - return; + return false; } sp<AMessage> notify = new AMessage(kWhatOMXMessage, mCodec->id()); observer->setNotificationMessage(notify); mCodec->mComponentName = componentName; + mCodec->mQuirks = quirks; mCodec->mOMX = omx; mCodec->mNode = node; @@ -1844,20 +2662,142 @@ void ACodec::UninitializedState::onSetup( mCodec->mInputEOSResult = OK; - mCodec->configureCodec(mime.c_str(), msg); + { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatComponentAllocated); + notify->setString("componentName", mCodec->mComponentName.c_str()); + notify->post(); + } + + mCodec->changeState(mCodec->mLoadedState); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::LoadedState::LoadedState(ACodec *codec) + : BaseState(codec) { +} + +void ACodec::LoadedState::stateEntered() { + ALOGV("[%s] Now Loaded", mCodec->mComponentName.c_str()); + + if (mCodec->mShutdownInProgress) { + bool keepComponentAllocated = mCodec->mKeepComponentAllocated; + + mCodec->mShutdownInProgress = false; + mCodec->mKeepComponentAllocated = false; + + onShutdown(keepComponentAllocated); + } +} + +void ACodec::LoadedState::onShutdown(bool keepComponentAllocated) { + if (!keepComponentAllocated) { + CHECK_EQ(mCodec->mOMX->freeNode(mCodec->mNode), (status_t)OK); + + mCodec->mNativeWindow.clear(); + mCodec->mNode = NULL; + mCodec->mOMX.clear(); + mCodec->mQuirks = 0; + mCodec->mComponentName.clear(); + + mCodec->changeState(mCodec->mUninitializedState); + } + + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatShutdownCompleted); + notify->post(); +} + +bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case ACodec::kWhatConfigureComponent: + { + onConfigureComponent(msg); + handled = true; + break; + } + + case ACodec::kWhatStart: + { + onStart(); + handled = true; + break; + } + + case ACodec::kWhatShutdown: + { + int32_t keepComponentAllocated; + CHECK(msg->findInt32( + "keepComponentAllocated", &keepComponentAllocated)); + + onShutdown(keepComponentAllocated); + + handled = true; + break; + } + + case ACodec::kWhatFlush: + { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->post(); + + handled = true; + break; + } + + default: + return BaseState::onMessageReceived(msg); + } + + return handled; +} + +bool ACodec::LoadedState::onConfigureComponent( + const sp<AMessage> &msg) { + ALOGV("onConfigureComponent"); + + CHECK(mCodec->mNode != NULL); + + AString mime; + CHECK(msg->findString("mime", &mime)); + + status_t err = mCodec->configureCodec(mime.c_str(), msg); + + if (err != OK) { + mCodec->signalError(OMX_ErrorUndefined, err); + return false; + } sp<RefBase> obj; if (msg->findObject("native-window", &obj) - && strncmp("OMX.google.", componentName.c_str(), 11)) { + && strncmp("OMX.google.", mCodec->mComponentName.c_str(), 11)) { sp<NativeWindowWrapper> nativeWindow( static_cast<NativeWindowWrapper *>(obj.get())); CHECK(nativeWindow != NULL); mCodec->mNativeWindow = nativeWindow->getNativeWindow(); } - CHECK_EQ((status_t)OK, mCodec->initNativeWindow()); - CHECK_EQ(omx->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle), + { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatComponentConfigured); + notify->post(); + } + + return true; +} + +void ACodec::LoadedState::onStart() { + ALOGV("onStart"); + + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandStateSet, OMX_StateIdle), (status_t)OK); mCodec->changeState(mCodec->mLoadedToIdleState); @@ -1878,7 +2818,7 @@ void ACodec::LoadedToIdleState::stateEntered() { "(error 0x%08x)", err); - mCodec->signalError(); + mCodec->signalError(OMX_ErrorUndefined, err); } } @@ -2042,6 +2982,13 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatShutdown: { + int32_t keepComponentAllocated; + CHECK(msg->findInt32( + "keepComponentAllocated", &keepComponentAllocated)); + + mCodec->mShutdownInProgress = true; + mCodec->mKeepComponentAllocated = keepComponentAllocated; + mActive = false; CHECK_EQ(mCodec->mOMX->sendCommand( @@ -2202,7 +3149,7 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( "port reconfiguration (error 0x%08x)", err); - mCodec->signalError(); + mCodec->signalError(OMX_ErrorUndefined, err); // This is technically not correct, since we were unable // to allocate output buffers and therefore the output port @@ -2240,7 +3187,8 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( //////////////////////////////////////////////////////////////////////////////// ACodec::ExecutingToIdleState::ExecutingToIdleState(ACodec *codec) - : BaseState(codec) { + : BaseState(codec), + mComponentNowIdle(false) { } bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) { @@ -2274,6 +3222,7 @@ bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) { void ACodec::ExecutingToIdleState::stateEntered() { ALOGV("[%s] Now Executing->Idle", mCodec->mComponentName.c_str()); + mComponentNowIdle = false; mCodec->mSentFormat = false; } @@ -2285,6 +3234,8 @@ bool ACodec::ExecutingToIdleState::onOMXEvent( CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); CHECK_EQ(data2, (OMX_U32)OMX_StateIdle); + mComponentNowIdle = true; + changeStateIfWeOwnAllBuffers(); return true; @@ -2303,7 +3254,7 @@ bool ACodec::ExecutingToIdleState::onOMXEvent( } void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() { - if (mCodec->allYourBuffersAreBelongToUs()) { + if (mComponentNowIdle && mCodec->allYourBuffersAreBelongToUs()) { CHECK_EQ(mCodec->mOMX->sendCommand( mCodec->mNode, OMX_CommandStateSet, OMX_StateLoaded), (status_t)OK); @@ -2375,20 +3326,7 @@ bool ACodec::IdleToLoadedState::onOMXEvent( CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); CHECK_EQ(data2, (OMX_U32)OMX_StateLoaded); - ALOGV("[%s] Now Loaded", mCodec->mComponentName.c_str()); - - CHECK_EQ(mCodec->mOMX->freeNode(mCodec->mNode), (status_t)OK); - - mCodec->mNativeWindow.clear(); - mCodec->mNode = NULL; - mCodec->mOMX.clear(); - mCodec->mComponentName.clear(); - - mCodec->changeState(mCodec->mUninitializedState); - - sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatShutdownCompleted); - notify->post(); + mCodec->changeState(mCodec->mLoadedState); return true; } diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 483e5ab..5aea8d0 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -29,12 +29,14 @@ LOCAL_SRC_FILES:= \ MPEG4Writer.cpp \ MediaBuffer.cpp \ MediaBufferGroup.cpp \ + MediaCodec.cpp \ + MediaCodecList.cpp \ MediaDefs.cpp \ MediaExtractor.cpp \ MediaSource.cpp \ - MediaSourceSplitter.cpp \ MetaData.cpp \ NuCachedSource2.cpp \ + NuMediaExtractor.cpp \ OMXClient.cpp \ OMXCodec.cpp \ OggExtractor.cpp \ @@ -56,25 +58,34 @@ LOCAL_SRC_FILES:= \ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ $(TOP)/frameworks/base/include/media/stagefright/openmax \ + $(TOP)/frameworks/base/include/media/stagefright/timedtext \ + $(TOP)/external/expat/lib \ $(TOP)/external/flac/include \ $(TOP)/external/tremolo \ $(TOP)/external/openssl/include \ LOCAL_SHARED_LIBRARIES := \ - libbinder \ - libmedia \ - libutils \ - libcutils \ - libui \ - libsonivox \ - libvorbisidec \ - libstagefright_yuv \ + libbinder \ libcamera_client \ - libdrmframework \ - libcrypto \ - libssl \ - libgui \ + libchromium_net \ + libcrypto \ + libcutils \ + libdl \ + libdrmframework \ + libexpat \ + libgui \ + libicui18n \ + libicuuc \ + liblog \ + libmedia \ + libsonivox \ + libssl \ libstagefright_omx \ + libstagefright_yuv \ + libui \ + libutils \ + libvorbisidec \ + libz \ LOCAL_STATIC_LIBRARIES := \ libstagefright_color_conversion \ @@ -88,57 +99,14 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_httplive \ libstagefright_id3 \ libFLAC \ + libstagefright_chromium_http \ -################################################################################ - -# The following was shamelessly copied from external/webkit/Android.mk and -# currently must follow the same logic to determine how webkit was built and -# if it's safe to link against libchromium.net - -# V8 also requires an ARMv7 CPU, and since we must use jsc, we cannot -# use the Chrome http stack either. -ifneq ($(strip $(ARCH_ARM_HAVE_ARMV7A)),true) - USE_ALT_HTTP := true -endif - -# See if the user has specified a stack they want to use -HTTP_STACK = $(HTTP) -# We default to the Chrome HTTP stack. -DEFAULT_HTTP = chrome -ALT_HTTP = android - -ifneq ($(HTTP_STACK),chrome) - ifneq ($(HTTP_STACK),android) - # No HTTP stack is specified, pickup the one we want as default. - ifeq ($(USE_ALT_HTTP),true) - HTTP_STACK = $(ALT_HTTP) - else - HTTP_STACK = $(DEFAULT_HTTP) - endif - endif -endif - -ifeq ($(HTTP_STACK),chrome) - -LOCAL_SHARED_LIBRARIES += \ - liblog \ - libicuuc \ - libicui18n \ - libz \ - libdl \ - -LOCAL_STATIC_LIBRARIES += \ - libstagefright_chromium_http - -LOCAL_SHARED_LIBRARIES += libstlport libchromium_net +LOCAL_SHARED_LIBRARIES += libstlport include external/stlport/libstlport.mk +# TODO: Chromium is always available, so this flag can be removed. LOCAL_CPPFLAGS += -DCHROMIUM_AVAILABLE=1 -endif # ifeq ($(HTTP_STACK),chrome) - -################################################################################ - LOCAL_SHARED_LIBRARIES += \ libstagefright_enc_common \ libstagefright_avc_common \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index df27566..650b6c4 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -110,13 +110,18 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) { success = format->findInt32(kKeySampleRate, &mSampleRate); CHECK(success); - int32_t numChannels; + int32_t numChannels, channelMask; success = format->findInt32(kKeyChannelCount, &numChannels); CHECK(success); + if(!format->findInt32(kKeyChannelMask, &channelMask)) { + ALOGW("source format didn't specify channel mask, using channel order"); + channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; + } + if (mAudioSink.get() != NULL) { status_t err = mAudioSink->open( - mSampleRate, numChannels, AUDIO_FORMAT_PCM_16_BIT, + mSampleRate, numChannels, channelMask, AUDIO_FORMAT_PCM_16_BIT, DEFAULT_AUDIOSINK_BUFFERCOUNT, &AudioPlayer::AudioSinkCallback, this); if (err != OK) { @@ -137,11 +142,15 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) { mAudioSink->start(); } else { + // playing to an AudioTrack, set up mask if necessary + audio_channel_mask_t audioMask = channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER ? + audio_channel_mask_from_count(numChannels) : channelMask; + if (0 == audioMask) { + return BAD_VALUE; + } + mAudioTrack = new AudioTrack( - AUDIO_STREAM_MUSIC, mSampleRate, AUDIO_FORMAT_PCM_16_BIT, - (numChannels == 2) - ? AUDIO_CHANNEL_OUT_STEREO - : AUDIO_CHANNEL_OUT_MONO, + AUDIO_STREAM_MUSIC, mSampleRate, AUDIO_FORMAT_PCM_16_BIT, audioMask, 0, 0, &AudioCallback, this, 0); if ((err = mAudioTrack->initCheck()) != OK) { @@ -418,6 +427,12 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) { break; } + if (mAudioSink != NULL) { + mLatencyUs = (int64_t)mAudioSink->latency() * 1000; + } else { + mLatencyUs = (int64_t)mAudioTrack->latency() * 1000; + } + CHECK(mInputBuffer->meta_data()->findInt64( kKeyTime, &mPositionTimeMediaUs)); diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp index fef2a00..5b2ea1f 100644 --- a/media/libstagefright/AudioSource.cpp +++ b/media/libstagefright/AudioSource.cpp @@ -282,8 +282,6 @@ status_t AudioSource::dataCallbackTimestamp( mPrevSampleTimeUs = mStartTimeUs; } - int64_t timestampUs = mPrevSampleTimeUs; - size_t numLostBytes = 0; if (mNumFramesReceived > 0) { // Ignore earlier frame lost // getInputFramesLost() returns the number of lost frames. @@ -293,37 +291,58 @@ status_t AudioSource::dataCallbackTimestamp( CHECK_EQ(numLostBytes & 1, 0u); CHECK_EQ(audioBuffer.size & 1, 0u); - size_t bufferSize = numLostBytes + audioBuffer.size; - MediaBuffer *buffer = new MediaBuffer(bufferSize); if (numLostBytes > 0) { - memset(buffer->data(), 0, numLostBytes); - memcpy((uint8_t *) buffer->data() + numLostBytes, - audioBuffer.i16, audioBuffer.size); - } else { - if (audioBuffer.size == 0) { - ALOGW("Nothing is available from AudioRecord callback buffer"); - buffer->release(); - return OK; + // Loss of audio frames should happen rarely; thus the LOGW should + // not cause a logging spam + ALOGW("Lost audio record data: %d bytes", numLostBytes); + } + + while (numLostBytes > 0) { + size_t bufferSize = numLostBytes; + if (numLostBytes > kMaxBufferSize) { + numLostBytes -= kMaxBufferSize; + bufferSize = kMaxBufferSize; + } else { + numLostBytes = 0; } - memcpy((uint8_t *) buffer->data(), - audioBuffer.i16, audioBuffer.size); + MediaBuffer *lostAudioBuffer = new MediaBuffer(bufferSize); + memset(lostAudioBuffer->data(), 0, bufferSize); + lostAudioBuffer->set_range(0, bufferSize); + queueInputBuffer_l(lostAudioBuffer, timeUs); + } + + if (audioBuffer.size == 0) { + ALOGW("Nothing is available from AudioRecord callback buffer"); + return OK; } + const size_t bufferSize = audioBuffer.size; + MediaBuffer *buffer = new MediaBuffer(bufferSize); + memcpy((uint8_t *) buffer->data(), + audioBuffer.i16, audioBuffer.size); buffer->set_range(0, bufferSize); - timestampUs += ((1000000LL * (bufferSize >> 1)) + - (mSampleRate >> 1)) / mSampleRate; + queueInputBuffer_l(buffer, timeUs); + return OK; +} + +void AudioSource::queueInputBuffer_l(MediaBuffer *buffer, int64_t timeUs) { + const size_t bufferSize = buffer->range_length(); + const size_t frameSize = mRecord->frameSize(); + const int64_t timestampUs = + mPrevSampleTimeUs + + ((1000000LL * (bufferSize / frameSize)) + + (mSampleRate >> 1)) / mSampleRate; if (mNumFramesReceived == 0) { buffer->meta_data()->setInt64(kKeyAnchorTime, mStartTimeUs); } + buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs); buffer->meta_data()->setInt64(kKeyDriftTime, timeUs - mInitialReadTimeUs); mPrevSampleTimeUs = timestampUs; - mNumFramesReceived += buffer->range_length() / sizeof(int16_t); + mNumFramesReceived += bufferSize / frameSize; mBuffersReceived.push_back(buffer); mFrameAvailableCondition.signal(); - - return OK; } void AudioSource::trackMaxAmplitude(int16_t *data, int nSamples) { diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 9c975b7..9e00bb3 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -30,13 +30,12 @@ #include "include/MPEG2TSExtractor.h" #include "include/WVMExtractor.h" -#include "timedtext/TimedTextDriver.h" - #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/timedtext/TimedTextDriver.h> #include <media/stagefright/AudioPlayer.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> @@ -47,10 +46,8 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXCodec.h> -#include <surfaceflinger/Surface.h> #include <gui/ISurfaceTexture.h> #include <gui/SurfaceTextureClient.h> -#include <surfaceflinger/ISurfaceComposer.h> #include <media/stagefright/foundation/AMessage.h> diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index ed1d5f4..2df5528 100755 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -27,7 +27,7 @@ #include <media/stagefright/MetaData.h> #include <camera/Camera.h> #include <camera/CameraParameters.h> -#include <surfaceflinger/Surface.h> +#include <gui/Surface.h> #include <utils/String8.h> #include <cutils/properties.h> diff --git a/media/libstagefright/MPEG2TSWriter.cpp b/media/libstagefright/MPEG2TSWriter.cpp index 0b4ecbe..f702376 100644 --- a/media/libstagefright/MPEG2TSWriter.cpp +++ b/media/libstagefright/MPEG2TSWriter.cpp @@ -244,7 +244,7 @@ void MPEG2TSWriter::SourceInfo::extractCodecSpecificData() { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kNotifyBuffer); - notify->setObject("buffer", out); + notify->setBuffer("buffer", out); notify->setInt32("oob", true); notify->post(); } @@ -270,7 +270,7 @@ void MPEG2TSWriter::SourceInfo::postAVCFrame(MediaBuffer *buffer) { copy->meta()->setInt32("isSync", true); } - notify->setObject("buffer", copy); + notify->setBuffer("buffer", copy); notify->post(); } @@ -351,7 +351,7 @@ bool MPEG2TSWriter::SourceInfo::flushAACFrames() { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kNotifyBuffer); - notify->setObject("buffer", mAACBuffer); + notify->setBuffer("buffer", mAACBuffer); notify->post(); mAACBuffer.clear(); @@ -614,10 +614,8 @@ void MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) { ++mNumSourcesDone; } else if (what == SourceInfo::kNotifyBuffer) { - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); int32_t oob; if (msg->findInt32("oob", &oob) && oob) { diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp new file mode 100644 index 0000000..a9e7f360 --- /dev/null +++ b/media/libstagefright/MediaCodec.cpp @@ -0,0 +1,1217 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaCodec" +#include <utils/Log.h> + +#include <media/stagefright/MediaCodec.h> + +#include "include/SoftwareRenderer.h" + +#include <gui/SurfaceTextureClient.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/NativeWindowWrapper.h> + +namespace android { + +// static +sp<MediaCodec> MediaCodec::CreateByType( + const sp<ALooper> &looper, const char *mime, bool encoder) { + sp<MediaCodec> codec = new MediaCodec(looper); + if (codec->init(mime, true /* nameIsType */, encoder) != OK) { + return NULL; + } + + return codec; +} + +// static +sp<MediaCodec> MediaCodec::CreateByComponentName( + const sp<ALooper> &looper, const char *name) { + sp<MediaCodec> codec = new MediaCodec(looper); + if (codec->init(name, false /* nameIsType */, false /* encoder */) != OK) { + return NULL; + } + + return codec; +} + +MediaCodec::MediaCodec(const sp<ALooper> &looper) + : mState(UNINITIALIZED), + mLooper(looper), + mCodec(new ACodec), + mFlags(0), + mSoftRenderer(NULL), + mDequeueInputTimeoutGeneration(0), + mDequeueInputReplyID(0), + mDequeueOutputTimeoutGeneration(0), + mDequeueOutputReplyID(0) { +} + +MediaCodec::~MediaCodec() { + CHECK_EQ(mState, UNINITIALIZED); +} + +// static +status_t MediaCodec::PostAndAwaitResponse( + const sp<AMessage> &msg, sp<AMessage> *response) { + status_t err = msg->postAndAwaitResponse(response); + + if (err != OK) { + return err; + } + + if (!(*response)->findInt32("err", &err)) { + err = OK; + } + + return err; +} + +status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) { + // Current video decoders do not return from OMX_FillThisBuffer + // quickly, violating the OpenMAX specs, until that is remedied + // we need to invest in an extra looper to free the main event + // queue. + bool needDedicatedLooper = false; + if (nameIsType && !strncasecmp(name, "video/", 6)) { + needDedicatedLooper = true; + } else if (!nameIsType && !strncmp(name, "OMX.TI.DUCATI1.VIDEO.", 21)) { + needDedicatedLooper = true; + } + + if (needDedicatedLooper) { + if (mCodecLooper == NULL) { + mCodecLooper = new ALooper; + mCodecLooper->setName("CodecLooper"); + mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO); + } + + mCodecLooper->registerHandler(mCodec); + } else { + mLooper->registerHandler(mCodec); + } + + mLooper->registerHandler(this); + + mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id())); + + sp<AMessage> msg = new AMessage(kWhatInit, id()); + msg->setString("name", name); + msg->setInt32("nameIsType", nameIsType); + + if (nameIsType) { + msg->setInt32("encoder", encoder); + } + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::configure( + const sp<AMessage> &format, + const sp<SurfaceTextureClient> &nativeWindow, + uint32_t flags) { + sp<AMessage> msg = new AMessage(kWhatConfigure, id()); + + msg->setMessage("format", format); + msg->setInt32("flags", flags); + + if (nativeWindow != NULL) { + if (!(mFlags & kFlagIsSoftwareCodec)) { + msg->setObject( + "native-window", + new NativeWindowWrapper(nativeWindow)); + } else { + mNativeWindow = nativeWindow; + } + } + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::start() { + sp<AMessage> msg = new AMessage(kWhatStart, id()); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::stop() { + sp<AMessage> msg = new AMessage(kWhatStop, id()); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::release() { + sp<AMessage> msg = new AMessage(kWhatRelease, id()); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::queueInputBuffer( + size_t index, + size_t offset, + size_t size, + int64_t presentationTimeUs, + uint32_t flags) { + sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, id()); + msg->setSize("index", index); + msg->setSize("offset", offset); + msg->setSize("size", size); + msg->setInt64("timeUs", presentationTimeUs); + msg->setInt32("flags", flags); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { + sp<AMessage> msg = new AMessage(kWhatDequeueInputBuffer, id()); + msg->setInt64("timeoutUs", timeoutUs); + + sp<AMessage> response; + status_t err; + if ((err = PostAndAwaitResponse(msg, &response)) != OK) { + return err; + } + + CHECK(response->findSize("index", index)); + + return OK; +} + +status_t MediaCodec::dequeueOutputBuffer( + size_t *index, + size_t *offset, + size_t *size, + int64_t *presentationTimeUs, + uint32_t *flags, + int64_t timeoutUs) { + sp<AMessage> msg = new AMessage(kWhatDequeueOutputBuffer, id()); + msg->setInt64("timeoutUs", timeoutUs); + + sp<AMessage> response; + status_t err; + if ((err = PostAndAwaitResponse(msg, &response)) != OK) { + return err; + } + + CHECK(response->findSize("index", index)); + CHECK(response->findSize("offset", offset)); + CHECK(response->findSize("size", size)); + CHECK(response->findInt64("timeUs", presentationTimeUs)); + CHECK(response->findInt32("flags", (int32_t *)flags)); + + return OK; +} + +status_t MediaCodec::renderOutputBufferAndRelease(size_t index) { + sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, id()); + msg->setSize("index", index); + msg->setInt32("render", true); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::releaseOutputBuffer(size_t index) { + sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, id()); + msg->setSize("index", index); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::getOutputFormat(sp<AMessage> *format) const { + sp<AMessage> msg = new AMessage(kWhatGetOutputFormat, id()); + + sp<AMessage> response; + status_t err; + if ((err = PostAndAwaitResponse(msg, &response)) != OK) { + return err; + } + + CHECK(response->findMessage("format", format)); + + return OK; +} + +status_t MediaCodec::getInputBuffers(Vector<sp<ABuffer> > *buffers) const { + sp<AMessage> msg = new AMessage(kWhatGetBuffers, id()); + msg->setInt32("portIndex", kPortIndexInput); + msg->setPointer("buffers", buffers); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::getOutputBuffers(Vector<sp<ABuffer> > *buffers) const { + sp<AMessage> msg = new AMessage(kWhatGetBuffers, id()); + msg->setInt32("portIndex", kPortIndexOutput); + msg->setPointer("buffers", buffers); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t MediaCodec::flush() { + sp<AMessage> msg = new AMessage(kWhatFlush, id()); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +//////////////////////////////////////////////////////////////////////////////// + +void MediaCodec::cancelPendingDequeueOperations() { + if (mFlags & kFlagDequeueInputPending) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + response->postReply(mDequeueInputReplyID); + + ++mDequeueInputTimeoutGeneration; + mDequeueInputReplyID = 0; + mFlags &= ~kFlagDequeueInputPending; + } + + if (mFlags & kFlagDequeueOutputPending) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + response->postReply(mDequeueOutputReplyID); + + ++mDequeueOutputTimeoutGeneration; + mDequeueOutputReplyID = 0; + mFlags &= ~kFlagDequeueOutputPending; + } +} + +bool MediaCodec::handleDequeueInputBuffer(uint32_t replyID, bool newRequest) { + if (mState != STARTED + || (mFlags & kFlagStickyError) + || (newRequest && (mFlags & kFlagDequeueInputPending))) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + + return true; + } + + ssize_t index = dequeuePortBuffer(kPortIndexInput); + + if (index < 0) { + CHECK_EQ(index, -EAGAIN); + return false; + } + + sp<AMessage> response = new AMessage; + response->setSize("index", index); + response->postReply(replyID); + + return true; +} + +bool MediaCodec::handleDequeueOutputBuffer(uint32_t replyID, bool newRequest) { + sp<AMessage> response = new AMessage; + + if (mState != STARTED + || (mFlags & kFlagStickyError) + || (newRequest && (mFlags & kFlagDequeueOutputPending))) { + response->setInt32("err", INVALID_OPERATION); + } else if (mFlags & kFlagOutputBuffersChanged) { + response->setInt32("err", INFO_OUTPUT_BUFFERS_CHANGED); + mFlags &= ~kFlagOutputBuffersChanged; + } else if (mFlags & kFlagOutputFormatChanged) { + response->setInt32("err", INFO_FORMAT_CHANGED); + mFlags &= ~kFlagOutputFormatChanged; + } else { + ssize_t index = dequeuePortBuffer(kPortIndexOutput); + + if (index < 0) { + CHECK_EQ(index, -EAGAIN); + return false; + } + + const sp<ABuffer> &buffer = + mPortBuffers[kPortIndexOutput].itemAt(index).mData; + + response->setSize("index", index); + response->setSize("offset", buffer->offset()); + response->setSize("size", buffer->size()); + + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + + response->setInt64("timeUs", timeUs); + + int32_t omxFlags; + CHECK(buffer->meta()->findInt32("omxFlags", &omxFlags)); + + uint32_t flags = 0; + if (omxFlags & OMX_BUFFERFLAG_SYNCFRAME) { + flags |= BUFFER_FLAG_SYNCFRAME; + } + if (omxFlags & OMX_BUFFERFLAG_CODECCONFIG) { + flags |= BUFFER_FLAG_CODECCONFIG; + } + if (omxFlags & OMX_BUFFERFLAG_EOS) { + flags |= BUFFER_FLAG_EOS; + } + + response->setInt32("flags", flags); + } + + response->postReply(replyID); + + return true; +} + +void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case ACodec::kWhatError: + { + int32_t omxError, internalError; + CHECK(msg->findInt32("omx-error", &omxError)); + CHECK(msg->findInt32("err", &internalError)); + + ALOGE("Codec reported an error. " + "(omx error 0x%08x, internalError %d)", + omxError, internalError); + + bool sendErrorReponse = true; + + switch (mState) { + case INITIALIZING: + { + setState(UNINITIALIZED); + break; + } + + case CONFIGURING: + { + setState(INITIALIZED); + break; + } + + case STARTING: + { + setState(CONFIGURED); + break; + } + + case STOPPING: + case RELEASING: + { + // Ignore the error, assuming we'll still get + // the shutdown complete notification. + + sendErrorReponse = false; + break; + } + + case FLUSHING: + { + setState(STARTED); + break; + } + + case STARTED: + { + sendErrorReponse = false; + + mFlags |= kFlagStickyError; + + cancelPendingDequeueOperations(); + break; + } + + default: + { + sendErrorReponse = false; + + mFlags |= kFlagStickyError; + break; + } + } + + if (sendErrorReponse) { + sp<AMessage> response = new AMessage; + response->setInt32("err", UNKNOWN_ERROR); + + response->postReply(mReplyID); + } + break; + } + + case ACodec::kWhatComponentAllocated: + { + CHECK_EQ(mState, INITIALIZING); + setState(INITIALIZED); + + AString componentName; + CHECK(msg->findString("componentName", &componentName)); + + if (componentName.startsWith("OMX.google.")) { + mFlags |= kFlagIsSoftwareCodec; + } else { + mFlags &= ~kFlagIsSoftwareCodec; + } + + (new AMessage)->postReply(mReplyID); + break; + } + + case ACodec::kWhatComponentConfigured: + { + CHECK_EQ(mState, CONFIGURING); + setState(CONFIGURED); + + (new AMessage)->postReply(mReplyID); + break; + } + + case ACodec::kWhatBuffersAllocated: + { + int32_t portIndex; + CHECK(msg->findInt32("portIndex", &portIndex)); + + ALOGV("%s buffers allocated", + portIndex == kPortIndexInput ? "input" : "output"); + + CHECK(portIndex == kPortIndexInput + || portIndex == kPortIndexOutput); + + mPortBuffers[portIndex].clear(); + + Vector<BufferInfo> *buffers = &mPortBuffers[portIndex]; + for (size_t i = 0;; ++i) { + AString name = StringPrintf("buffer-id_%d", i); + + void *bufferID; + if (!msg->findPointer(name.c_str(), &bufferID)) { + break; + } + + name = StringPrintf("data_%d", i); + + BufferInfo info; + info.mBufferID = bufferID; + info.mOwnedByClient = false; + CHECK(msg->findBuffer(name.c_str(), &info.mData)); + + buffers->push_back(info); + } + + if (portIndex == kPortIndexOutput) { + if (mState == STARTING) { + // We're always allocating output buffers after + // allocating input buffers, so this is a good + // indication that now all buffers are allocated. + setState(STARTED); + (new AMessage)->postReply(mReplyID); + } else { + mFlags |= kFlagOutputBuffersChanged; + } + } + break; + } + + case ACodec::kWhatOutputFormatChanged: + { + ALOGV("codec output format changed"); + + if ((mFlags & kFlagIsSoftwareCodec) + && mNativeWindow != NULL) { + AString mime; + CHECK(msg->findString("mime", &mime)); + + if (!strncasecmp("video/", mime.c_str(), 6)) { + delete mSoftRenderer; + mSoftRenderer = NULL; + + int32_t width, height; + CHECK(msg->findInt32("width", &width)); + CHECK(msg->findInt32("height", &height)); + + int32_t colorFormat; + CHECK(msg->findInt32( + "color-format", &colorFormat)); + + sp<MetaData> meta = new MetaData; + meta->setInt32(kKeyWidth, width); + meta->setInt32(kKeyHeight, height); + meta->setInt32(kKeyColorFormat, colorFormat); + + mSoftRenderer = + new SoftwareRenderer(mNativeWindow, meta); + } + } + + mOutputFormat = msg; + mFlags |= kFlagOutputFormatChanged; + break; + } + + case ACodec::kWhatFillThisBuffer: + { + /* size_t index = */updateBuffers(kPortIndexInput, msg); + + if (mState == FLUSHING + || mState == STOPPING + || mState == RELEASING) { + returnBuffersToCodecOnPort(kPortIndexInput); + break; + } + + if (mFlags & kFlagDequeueInputPending) { + CHECK(handleDequeueInputBuffer(mDequeueInputReplyID)); + + ++mDequeueInputTimeoutGeneration; + mFlags &= ~kFlagDequeueInputPending; + mDequeueInputReplyID = 0; + } + break; + } + + case ACodec::kWhatDrainThisBuffer: + { + /* size_t index = */updateBuffers(kPortIndexOutput, msg); + + if (mState == FLUSHING + || mState == STOPPING + || mState == RELEASING) { + returnBuffersToCodecOnPort(kPortIndexOutput); + break; + } + + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + int32_t omxFlags; + CHECK(msg->findInt32("flags", &omxFlags)); + + buffer->meta()->setInt32("omxFlags", omxFlags); + + if (mFlags & kFlagDequeueOutputPending) { + CHECK(handleDequeueOutputBuffer(mDequeueOutputReplyID)); + + ++mDequeueOutputTimeoutGeneration; + mFlags &= ~kFlagDequeueOutputPending; + mDequeueOutputReplyID = 0; + } + break; + } + + case ACodec::kWhatEOS: + { + // We already notify the client of this by using the + // corresponding flag in "onOutputBufferReady". + break; + } + + case ACodec::kWhatShutdownCompleted: + { + if (mState == STOPPING) { + setState(INITIALIZED); + } else { + CHECK_EQ(mState, RELEASING); + setState(UNINITIALIZED); + } + + (new AMessage)->postReply(mReplyID); + break; + } + + case ACodec::kWhatFlushCompleted: + { + CHECK_EQ(mState, FLUSHING); + setState(STARTED); + + mCodec->signalResume(); + + (new AMessage)->postReply(mReplyID); + break; + } + + default: + TRESPASS(); + } + break; + } + + case kWhatInit: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != UNINITIALIZED) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(INITIALIZING); + + AString name; + CHECK(msg->findString("name", &name)); + + int32_t nameIsType; + int32_t encoder = false; + CHECK(msg->findInt32("nameIsType", &nameIsType)); + if (nameIsType) { + CHECK(msg->findInt32("encoder", &encoder)); + } + + sp<AMessage> format = new AMessage; + + if (nameIsType) { + format->setString("mime", name.c_str()); + format->setInt32("encoder", encoder); + } else { + format->setString("componentName", name.c_str()); + } + + mCodec->initiateAllocateComponent(format); + break; + } + + case kWhatConfigure: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != INITIALIZED) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(CONFIGURING); + + sp<RefBase> obj; + if (!msg->findObject("native-window", &obj)) { + obj.clear(); + } + + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); + + if (obj != NULL) { + format->setObject("native-window", obj); + } + + uint32_t flags; + CHECK(msg->findInt32("flags", (int32_t *)&flags)); + + if (flags & CONFIGURE_FLAG_ENCODE) { + format->setInt32("encoder", true); + } + + mCodec->initiateConfigureComponent(format); + break; + } + + case kWhatStart: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != CONFIGURED) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(STARTING); + + mCodec->initiateStart(); + break; + } + + case kWhatStop: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != INITIALIZED + && mState != CONFIGURED && mState != STARTED) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(STOPPING); + + mCodec->initiateShutdown(true /* keepComponentAllocated */); + returnBuffersToCodec(); + break; + } + + case kWhatRelease: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != INITIALIZED + && mState != CONFIGURED && mState != STARTED) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(RELEASING); + + mCodec->initiateShutdown(); + returnBuffersToCodec(); + break; + } + + case kWhatDequeueInputBuffer: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (handleDequeueInputBuffer(replyID, true /* new request */)) { + break; + } + + int64_t timeoutUs; + CHECK(msg->findInt64("timeoutUs", &timeoutUs)); + + if (timeoutUs == 0ll) { + sp<AMessage> response = new AMessage; + response->setInt32("err", -EAGAIN); + response->postReply(replyID); + break; + } + + mFlags |= kFlagDequeueInputPending; + mDequeueInputReplyID = replyID; + + if (timeoutUs > 0ll) { + sp<AMessage> timeoutMsg = + new AMessage(kWhatDequeueInputTimedOut, id()); + timeoutMsg->setInt32( + "generation", ++mDequeueInputTimeoutGeneration); + timeoutMsg->post(timeoutUs); + } + break; + } + + case kWhatDequeueInputTimedOut: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mDequeueInputTimeoutGeneration) { + // Obsolete + break; + } + + CHECK(mFlags & kFlagDequeueInputPending); + + sp<AMessage> response = new AMessage; + response->setInt32("err", -EAGAIN); + response->postReply(mDequeueInputReplyID); + + mFlags &= ~kFlagDequeueInputPending; + mDequeueInputReplyID = 0; + break; + } + + case kWhatQueueInputBuffer: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != STARTED || (mFlags & kFlagStickyError)) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + status_t err = onQueueInputBuffer(msg); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatDequeueOutputBuffer: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (handleDequeueOutputBuffer(replyID, true /* new request */)) { + break; + } + + int64_t timeoutUs; + CHECK(msg->findInt64("timeoutUs", &timeoutUs)); + + if (timeoutUs == 0ll) { + sp<AMessage> response = new AMessage; + response->setInt32("err", -EAGAIN); + response->postReply(replyID); + break; + } + + mFlags |= kFlagDequeueOutputPending; + mDequeueOutputReplyID = replyID; + + if (timeoutUs > 0ll) { + sp<AMessage> timeoutMsg = + new AMessage(kWhatDequeueOutputTimedOut, id()); + timeoutMsg->setInt32( + "generation", ++mDequeueOutputTimeoutGeneration); + timeoutMsg->post(timeoutUs); + } + break; + } + + case kWhatDequeueOutputTimedOut: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mDequeueOutputTimeoutGeneration) { + // Obsolete + break; + } + + CHECK(mFlags & kFlagDequeueOutputPending); + + sp<AMessage> response = new AMessage; + response->setInt32("err", -EAGAIN); + response->postReply(mDequeueOutputReplyID); + + mFlags &= ~kFlagDequeueOutputPending; + mDequeueOutputReplyID = 0; + break; + } + + case kWhatReleaseOutputBuffer: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != STARTED || (mFlags & kFlagStickyError)) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + status_t err = onReleaseOutputBuffer(msg); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatGetBuffers: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != STARTED || (mFlags & kFlagStickyError)) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + int32_t portIndex; + CHECK(msg->findInt32("portIndex", &portIndex)); + + Vector<sp<ABuffer> > *dstBuffers; + CHECK(msg->findPointer("buffers", (void **)&dstBuffers)); + + dstBuffers->clear(); + const Vector<BufferInfo> &srcBuffers = mPortBuffers[portIndex]; + + for (size_t i = 0; i < srcBuffers.size(); ++i) { + const BufferInfo &info = srcBuffers.itemAt(i); + + dstBuffers->push_back(info.mData); + } + + (new AMessage)->postReply(replyID); + break; + } + + case kWhatFlush: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if (mState != STARTED || (mFlags & kFlagStickyError)) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + mReplyID = replyID; + setState(FLUSHING); + + mCodec->signalFlush(); + returnBuffersToCodec(); + break; + } + + case kWhatGetOutputFormat: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + if ((mState != STARTED && mState != FLUSHING) + || (mFlags & kFlagStickyError)) { + sp<AMessage> response = new AMessage; + response->setInt32("err", INVALID_OPERATION); + + response->postReply(replyID); + break; + } + + sp<AMessage> response = new AMessage; + response->setMessage("format", mOutputFormat); + response->postReply(replyID); + break; + } + + default: + TRESPASS(); + } +} + +void MediaCodec::setState(State newState) { + if (newState == UNINITIALIZED) { + delete mSoftRenderer; + mSoftRenderer = NULL; + + mNativeWindow.clear(); + + mOutputFormat.clear(); + mFlags &= ~kFlagOutputFormatChanged; + mFlags &= ~kFlagOutputBuffersChanged; + mFlags &= ~kFlagStickyError; + } + + mState = newState; + + cancelPendingDequeueOperations(); +} + +void MediaCodec::returnBuffersToCodec() { + returnBuffersToCodecOnPort(kPortIndexInput); + returnBuffersToCodecOnPort(kPortIndexOutput); +} + +void MediaCodec::returnBuffersToCodecOnPort(int32_t portIndex) { + CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput); + + Vector<BufferInfo> *buffers = &mPortBuffers[portIndex]; + + for (size_t i = 0; i < buffers->size(); ++i) { + BufferInfo *info = &buffers->editItemAt(i); + + if (info->mNotify != NULL) { + sp<AMessage> msg = info->mNotify; + info->mNotify = NULL; + info->mOwnedByClient = false; + + if (portIndex == kPortIndexInput) { + msg->setInt32("err", ERROR_END_OF_STREAM); + } + msg->post(); + } + } + + mAvailPortBuffers[portIndex].clear(); +} + +size_t MediaCodec::updateBuffers( + int32_t portIndex, const sp<AMessage> &msg) { + CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput); + + void *bufferID; + CHECK(msg->findPointer("buffer-id", &bufferID)); + + Vector<BufferInfo> *buffers = &mPortBuffers[portIndex]; + + for (size_t i = 0; i < buffers->size(); ++i) { + BufferInfo *info = &buffers->editItemAt(i); + + if (info->mBufferID == bufferID) { + CHECK(info->mNotify == NULL); + CHECK(msg->findMessage("reply", &info->mNotify)); + + mAvailPortBuffers[portIndex].push_back(i); + + return i; + } + } + + TRESPASS(); + + return 0; +} + +status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) { + size_t index; + size_t offset; + size_t size; + int64_t timeUs; + uint32_t flags; + CHECK(msg->findSize("index", &index)); + CHECK(msg->findSize("offset", &offset)); + CHECK(msg->findSize("size", &size)); + CHECK(msg->findInt64("timeUs", &timeUs)); + CHECK(msg->findInt32("flags", (int32_t *)&flags)); + + if (index >= mPortBuffers[kPortIndexInput].size()) { + return -ERANGE; + } + + BufferInfo *info = &mPortBuffers[kPortIndexInput].editItemAt(index); + + if (info->mNotify == NULL || !info->mOwnedByClient) { + return -EACCES; + } + + if (offset + size > info->mData->capacity()) { + return -EINVAL; + } + + sp<AMessage> reply = info->mNotify; + info->mNotify = NULL; + info->mOwnedByClient = false; + + info->mData->setRange(offset, size); + info->mData->meta()->setInt64("timeUs", timeUs); + + if (flags & BUFFER_FLAG_EOS) { + info->mData->meta()->setInt32("eos", true); + } + + if (flags & BUFFER_FLAG_CODECCONFIG) { + info->mData->meta()->setInt32("csd", true); + } + + reply->setBuffer("buffer", info->mData); + reply->post(); + + return OK; +} + +status_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) { + size_t index; + CHECK(msg->findSize("index", &index)); + + int32_t render; + if (!msg->findInt32("render", &render)) { + render = 0; + } + + if (mState != STARTED) { + return -EINVAL; + } + + if (index >= mPortBuffers[kPortIndexOutput].size()) { + return -ERANGE; + } + + BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index); + + if (info->mNotify == NULL || !info->mOwnedByClient) { + return -EACCES; + } + + if (render) { + info->mNotify->setInt32("render", true); + + if (mSoftRenderer != NULL) { + mSoftRenderer->render( + info->mData->data(), info->mData->size(), NULL); + } + } + + info->mNotify->post(); + info->mNotify = NULL; + info->mOwnedByClient = false; + + return OK; +} + +ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) { + CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput); + + List<size_t> *availBuffers = &mAvailPortBuffers[portIndex]; + + if (availBuffers->empty()) { + return -EAGAIN; + } + + size_t index = *availBuffers->begin(); + availBuffers->erase(availBuffers->begin()); + + BufferInfo *info = &mPortBuffers[portIndex].editItemAt(index); + CHECK(!info->mOwnedByClient); + info->mOwnedByClient = true; + + return index; +} + +} // namespace android diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp new file mode 100644 index 0000000..6b64e21 --- /dev/null +++ b/media/libstagefright/MediaCodecList.cpp @@ -0,0 +1,475 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaCodecList" +#include <utils/Log.h> + +#include <media/stagefright/MediaCodecList.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +#include <expat.h> + +namespace android { + +static Mutex sInitMutex; + +// static +MediaCodecList *MediaCodecList::sCodecList; + +// static +const MediaCodecList *MediaCodecList::getInstance() { + Mutex::Autolock autoLock(sInitMutex); + + if (sCodecList == NULL) { + sCodecList = new MediaCodecList; + } + + return sCodecList->initCheck() == OK ? sCodecList : NULL; +} + +MediaCodecList::MediaCodecList() + : mInitCheck(NO_INIT) { + FILE *file = fopen("/etc/media_codecs.xml", "r"); + + if (file == NULL) { + ALOGW("unable to open media codecs configuration xml file."); + return; + } + + parseXMLFile(file); + + if (mInitCheck == OK) { + // These are currently still used by the video editing suite. + + addMediaCodec(true /* encoder */, "AACEncoder", "audio/mp4a-latm"); + addMediaCodec(true /* encoder */, "AVCEncoder", "video/avc"); + + addMediaCodec(true /* encoder */, "M4vH263Encoder"); + addType("video/3gpp"); + addType("video/mp4v-es"); + } + +#if 0 + for (size_t i = 0; i < mCodecInfos.size(); ++i) { + const CodecInfo &info = mCodecInfos.itemAt(i); + + AString line = info.mName; + line.append(" supports "); + for (size_t j = 0; j < mTypes.size(); ++j) { + uint32_t value = mTypes.valueAt(j); + + if (info.mTypes & (1ul << value)) { + line.append(mTypes.keyAt(j)); + line.append(" "); + } + } + + ALOGI("%s", line.c_str()); + } +#endif + + fclose(file); + file = NULL; +} + +MediaCodecList::~MediaCodecList() { +} + +status_t MediaCodecList::initCheck() const { + return mInitCheck; +} + +void MediaCodecList::parseXMLFile(FILE *file) { + mInitCheck = OK; + mCurrentSection = SECTION_TOPLEVEL; + mDepth = 0; + + XML_Parser parser = ::XML_ParserCreate(NULL); + CHECK(parser != NULL); + + ::XML_SetUserData(parser, this); + ::XML_SetElementHandler( + parser, StartElementHandlerWrapper, EndElementHandlerWrapper); + + const int BUFF_SIZE = 512; + while (mInitCheck == OK) { + void *buff = ::XML_GetBuffer(parser, BUFF_SIZE); + if (buff == NULL) { + ALOGE("failed to in call to XML_GetBuffer()"); + mInitCheck = UNKNOWN_ERROR; + break; + } + + int bytes_read = ::fread(buff, 1, BUFF_SIZE, file); + if (bytes_read < 0) { + ALOGE("failed in call to read"); + mInitCheck = ERROR_IO; + break; + } + + if (::XML_ParseBuffer(parser, bytes_read, bytes_read == 0) + != XML_STATUS_OK) { + mInitCheck = ERROR_MALFORMED; + break; + } + + if (bytes_read == 0) { + break; + } + } + + ::XML_ParserFree(parser); + + if (mInitCheck == OK) { + for (size_t i = mCodecInfos.size(); i-- > 0;) { + CodecInfo *info = &mCodecInfos.editItemAt(i); + + if (info->mTypes == 0) { + // No types supported by this component??? + + ALOGW("Component %s does not support any type of media?", + info->mName.c_str()); + + mCodecInfos.removeAt(i); + } + } + } + + if (mInitCheck != OK) { + mCodecInfos.clear(); + mCodecQuirks.clear(); + } +} + +// static +void MediaCodecList::StartElementHandlerWrapper( + void *me, const char *name, const char **attrs) { + static_cast<MediaCodecList *>(me)->startElementHandler(name, attrs); +} + +// static +void MediaCodecList::EndElementHandlerWrapper(void *me, const char *name) { + static_cast<MediaCodecList *>(me)->endElementHandler(name); +} + +void MediaCodecList::startElementHandler( + const char *name, const char **attrs) { + if (mInitCheck != OK) { + return; + } + + switch (mCurrentSection) { + case SECTION_TOPLEVEL: + { + if (!strcmp(name, "Decoders")) { + mCurrentSection = SECTION_DECODERS; + } else if (!strcmp(name, "Encoders")) { + mCurrentSection = SECTION_ENCODERS; + } + break; + } + + case SECTION_DECODERS: + { + if (!strcmp(name, "MediaCodec")) { + mInitCheck = + addMediaCodecFromAttributes(false /* encoder */, attrs); + + mCurrentSection = SECTION_DECODER; + } + break; + } + + case SECTION_ENCODERS: + { + if (!strcmp(name, "MediaCodec")) { + mInitCheck = + addMediaCodecFromAttributes(true /* encoder */, attrs); + + mCurrentSection = SECTION_ENCODER; + } + break; + } + + case SECTION_DECODER: + case SECTION_ENCODER: + { + if (!strcmp(name, "Quirk")) { + mInitCheck = addQuirk(attrs); + } else if (!strcmp(name, "Type")) { + mInitCheck = addTypeFromAttributes(attrs); + } + break; + } + + default: + break; + } + + ++mDepth; +} + +void MediaCodecList::endElementHandler(const char *name) { + if (mInitCheck != OK) { + return; + } + + switch (mCurrentSection) { + case SECTION_DECODERS: + { + if (!strcmp(name, "Decoders")) { + mCurrentSection = SECTION_TOPLEVEL; + } + break; + } + + case SECTION_ENCODERS: + { + if (!strcmp(name, "Encoders")) { + mCurrentSection = SECTION_TOPLEVEL; + } + break; + } + + case SECTION_DECODER: + { + if (!strcmp(name, "MediaCodec")) { + mCurrentSection = SECTION_DECODERS; + } + break; + } + + case SECTION_ENCODER: + { + if (!strcmp(name, "MediaCodec")) { + mCurrentSection = SECTION_ENCODERS; + } + break; + } + + default: + break; + } + + --mDepth; +} + +status_t MediaCodecList::addMediaCodecFromAttributes( + bool encoder, const char **attrs) { + const char *name = NULL; + const char *type = NULL; + + size_t i = 0; + while (attrs[i] != NULL) { + if (!strcmp(attrs[i], "name")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + name = attrs[i + 1]; + ++i; + } else if (!strcmp(attrs[i], "type")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + type = attrs[i + 1]; + ++i; + } else { + return -EINVAL; + } + + ++i; + } + + if (name == NULL) { + return -EINVAL; + } + + addMediaCodec(encoder, name, type); + + return OK; +} + +void MediaCodecList::addMediaCodec( + bool encoder, const char *name, const char *type) { + mCodecInfos.push(); + CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); + info->mName = name; + info->mIsEncoder = encoder; + info->mTypes = 0; + info->mQuirks = 0; + + if (type != NULL) { + addType(type); + } +} + +status_t MediaCodecList::addQuirk(const char **attrs) { + const char *name = NULL; + + size_t i = 0; + while (attrs[i] != NULL) { + if (!strcmp(attrs[i], "name")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + name = attrs[i + 1]; + ++i; + } else { + return -EINVAL; + } + + ++i; + } + + if (name == NULL) { + return -EINVAL; + } + + uint32_t bit; + ssize_t index = mCodecQuirks.indexOfKey(name); + if (index < 0) { + bit = mCodecQuirks.size(); + + if (bit == 32) { + ALOGW("Too many distinct quirk names in configuration."); + return OK; + } + + mCodecQuirks.add(name, bit); + } else { + bit = mCodecQuirks.valueAt(index); + } + + CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); + info->mQuirks |= 1ul << bit; + + return OK; +} + +status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { + const char *name = NULL; + + size_t i = 0; + while (attrs[i] != NULL) { + if (!strcmp(attrs[i], "name")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + name = attrs[i + 1]; + ++i; + } else { + return -EINVAL; + } + + ++i; + } + + if (name == NULL) { + return -EINVAL; + } + + addType(name); + + return OK; +} + +void MediaCodecList::addType(const char *name) { + uint32_t bit; + ssize_t index = mTypes.indexOfKey(name); + if (index < 0) { + bit = mTypes.size(); + + if (bit == 32) { + ALOGW("Too many distinct type names in configuration."); + return; + } + + mTypes.add(name, bit); + } else { + bit = mTypes.valueAt(index); + } + + CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); + info->mTypes |= 1ul << bit; +} + +ssize_t MediaCodecList::findCodecByType( + const char *type, bool encoder, size_t startIndex) const { + ssize_t typeIndex = mTypes.indexOfKey(type); + + if (typeIndex < 0) { + return -ENOENT; + } + + uint32_t typeMask = 1ul << mTypes.valueAt(typeIndex); + + while (startIndex < mCodecInfos.size()) { + const CodecInfo &info = mCodecInfos.itemAt(startIndex); + + if (info.mIsEncoder == encoder && (info.mTypes & typeMask)) { + return startIndex; + } + + ++startIndex; + } + + return -ENOENT; +} + +ssize_t MediaCodecList::findCodecByName(const char *name) const { + for (size_t i = 0; i < mCodecInfos.size(); ++i) { + const CodecInfo &info = mCodecInfos.itemAt(i); + + if (info.mName == name) { + return i; + } + } + + return -ENOENT; +} + +const char *MediaCodecList::getCodecName(size_t index) const { + if (index >= mCodecInfos.size()) { + return NULL; + } + + const CodecInfo &info = mCodecInfos.itemAt(index); + return info.mName.c_str(); +} + +bool MediaCodecList::codecHasQuirk( + size_t index, const char *quirkName) const { + if (index >= mCodecInfos.size()) { + return NULL; + } + + const CodecInfo &info = mCodecInfos.itemAt(index); + + if (info.mQuirks != 0) { + ssize_t index = mCodecQuirks.indexOfKey(quirkName); + if (index >= 0 && info.mQuirks & (1ul << mCodecQuirks.valueAt(index))) { + return true; + } + } + + return false; +} + +} // namespace android diff --git a/media/libstagefright/MediaSourceSplitter.cpp b/media/libstagefright/MediaSourceSplitter.cpp deleted file mode 100644 index 3b64ded..0000000 --- a/media/libstagefright/MediaSourceSplitter.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "MediaSourceSplitter" -#include <utils/Log.h> - -#include <media/stagefright/foundation/ADebug.h> -#include <media/stagefright/MediaSourceSplitter.h> -#include <media/stagefright/MediaBuffer.h> -#include <media/stagefright/MetaData.h> - -namespace android { - -MediaSourceSplitter::MediaSourceSplitter(sp<MediaSource> mediaSource) { - mNumberOfClients = 0; - mSource = mediaSource; - mSourceStarted = false; - - mNumberOfClientsStarted = 0; - mNumberOfCurrentReads = 0; - mCurrentReadBit = 0; - mLastReadCompleted = true; -} - -MediaSourceSplitter::~MediaSourceSplitter() { -} - -sp<MediaSource> MediaSourceSplitter::createClient() { - Mutex::Autolock autoLock(mLock); - - sp<MediaSource> client = new Client(this, mNumberOfClients++); - mClientsStarted.push(false); - mClientsDesiredReadBit.push(0); - return client; -} - -status_t MediaSourceSplitter::start(int clientId, MetaData *params) { - Mutex::Autolock autoLock(mLock); - - ALOGV("start client (%d)", clientId); - if (mClientsStarted[clientId]) { - return OK; - } - - mNumberOfClientsStarted++; - - if (!mSourceStarted) { - ALOGV("Starting real source from client (%d)", clientId); - status_t err = mSource->start(params); - - if (err == OK) { - mSourceStarted = true; - mClientsStarted.editItemAt(clientId) = true; - mClientsDesiredReadBit.editItemAt(clientId) = !mCurrentReadBit; - } - - return err; - } else { - mClientsStarted.editItemAt(clientId) = true; - if (mLastReadCompleted) { - // Last read was completed. So join in the threads for the next read. - mClientsDesiredReadBit.editItemAt(clientId) = !mCurrentReadBit; - } else { - // Last read is ongoing. So join in the threads for the current read. - mClientsDesiredReadBit.editItemAt(clientId) = mCurrentReadBit; - } - return OK; - } -} - -status_t MediaSourceSplitter::stop(int clientId) { - Mutex::Autolock autoLock(mLock); - - ALOGV("stop client (%d)", clientId); - CHECK(clientId >= 0 && clientId < mNumberOfClients); - CHECK(mClientsStarted[clientId]); - - if (--mNumberOfClientsStarted == 0) { - ALOGV("Stopping real source from client (%d)", clientId); - status_t err = mSource->stop(); - mSourceStarted = false; - mClientsStarted.editItemAt(clientId) = false; - return err; - } else { - mClientsStarted.editItemAt(clientId) = false; - if (!mLastReadCompleted && (mClientsDesiredReadBit[clientId] == mCurrentReadBit)) { - // !mLastReadCompleted implies that buffer has been read from source, but all - // clients haven't read it. - // mClientsDesiredReadBit[clientId] == mCurrentReadBit implies that this - // client would have wanted to read from this buffer. (i.e. it has not yet - // called read() for the current read buffer.) - // Since other threads may be waiting for all the clients' reads to complete, - // signal that this read has been aborted. - signalReadComplete_lock(true); - } - return OK; - } -} - -sp<MetaData> MediaSourceSplitter::getFormat(int clientId) { - Mutex::Autolock autoLock(mLock); - - ALOGV("getFormat client (%d)", clientId); - return mSource->getFormat(); -} - -status_t MediaSourceSplitter::read(int clientId, - MediaBuffer **buffer, const MediaSource::ReadOptions *options) { - Mutex::Autolock autoLock(mLock); - - CHECK(clientId >= 0 && clientId < mNumberOfClients); - - ALOGV("read client (%d)", clientId); - *buffer = NULL; - - if (!mClientsStarted[clientId]) { - return OK; - } - - if (mCurrentReadBit != mClientsDesiredReadBit[clientId]) { - // Desired buffer has not been read from source yet. - - // If the current client is the special client with clientId = 0 - // then read from source, else wait until the client 0 has finished - // reading from source. - if (clientId == 0) { - // Wait for all client's last read to complete first so as to not - // corrupt the buffer at mLastReadMediaBuffer. - waitForAllClientsLastRead_lock(clientId); - - readFromSource_lock(options); - *buffer = mLastReadMediaBuffer; - } else { - waitForReadFromSource_lock(clientId); - - *buffer = mLastReadMediaBuffer; - (*buffer)->add_ref(); - } - CHECK(mCurrentReadBit == mClientsDesiredReadBit[clientId]); - } else { - // Desired buffer has already been read from source. Use the cached data. - CHECK(clientId != 0); - - *buffer = mLastReadMediaBuffer; - (*buffer)->add_ref(); - } - - mClientsDesiredReadBit.editItemAt(clientId) = !mClientsDesiredReadBit[clientId]; - signalReadComplete_lock(false); - - return mLastReadStatus; -} - -void MediaSourceSplitter::readFromSource_lock(const MediaSource::ReadOptions *options) { - mLastReadStatus = mSource->read(&mLastReadMediaBuffer , options); - - mCurrentReadBit = !mCurrentReadBit; - mLastReadCompleted = false; - mReadFromSourceCondition.broadcast(); -} - -void MediaSourceSplitter::waitForReadFromSource_lock(int32_t clientId) { - mReadFromSourceCondition.wait(mLock); -} - -void MediaSourceSplitter::waitForAllClientsLastRead_lock(int32_t clientId) { - if (mLastReadCompleted) { - return; - } - mAllReadsCompleteCondition.wait(mLock); - CHECK(mLastReadCompleted); -} - -void MediaSourceSplitter::signalReadComplete_lock(bool readAborted) { - if (!readAborted) { - mNumberOfCurrentReads++; - } - - if (mNumberOfCurrentReads == mNumberOfClientsStarted) { - mLastReadCompleted = true; - mNumberOfCurrentReads = 0; - mAllReadsCompleteCondition.broadcast(); - } -} - -status_t MediaSourceSplitter::pause(int clientId) { - return ERROR_UNSUPPORTED; -} - -// Client - -MediaSourceSplitter::Client::Client( - sp<MediaSourceSplitter> splitter, - int32_t clientId) { - mSplitter = splitter; - mClientId = clientId; -} - -status_t MediaSourceSplitter::Client::start(MetaData *params) { - return mSplitter->start(mClientId, params); -} - -status_t MediaSourceSplitter::Client::stop() { - return mSplitter->stop(mClientId); -} - -sp<MetaData> MediaSourceSplitter::Client::getFormat() { - return mSplitter->getFormat(mClientId); -} - -status_t MediaSourceSplitter::Client::read( - MediaBuffer **buffer, const ReadOptions *options) { - return mSplitter->read(mClientId, buffer, options); -} - -status_t MediaSourceSplitter::Client::pause() { - return mSplitter->pause(mClientId); -} - -} // namespace android diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp new file mode 100644 index 0000000..afd4763 --- /dev/null +++ b/media/libstagefright/NuMediaExtractor.cpp @@ -0,0 +1,433 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuMediaExtractor" +#include <utils/Log.h> + +#include <media/stagefright/NuMediaExtractor.h> + +#include "include/ESDS.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +NuMediaExtractor::NuMediaExtractor() { +} + +NuMediaExtractor::~NuMediaExtractor() { + releaseTrackSamples(); + + for (size_t i = 0; i < mSelectedTracks.size(); ++i) { + TrackInfo *info = &mSelectedTracks.editItemAt(i); + + CHECK_EQ((status_t)OK, info->mSource->stop()); + } + + mSelectedTracks.clear(); +} + +status_t NuMediaExtractor::setDataSource(const char *path) { + sp<DataSource> dataSource = DataSource::CreateFromURI(path); + + if (dataSource == NULL) { + return -ENOENT; + } + + mImpl = MediaExtractor::Create(dataSource); + + if (mImpl == NULL) { + return ERROR_UNSUPPORTED; + } + + return OK; +} + +size_t NuMediaExtractor::countTracks() const { + return mImpl == NULL ? 0 : mImpl->countTracks(); +} + +status_t NuMediaExtractor::getTrackFormat( + size_t index, sp<AMessage> *format) const { + *format = NULL; + + if (mImpl == NULL) { + return -EINVAL; + } + + if (index >= mImpl->countTracks()) { + return -ERANGE; + } + + sp<MetaData> meta = mImpl->getTrackMetaData(index); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> msg = new AMessage; + msg->setString("mime", mime); + + if (!strncasecmp("video/", mime, 6)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + msg->setInt32("width", width); + msg->setInt32("height", height); + } else { + CHECK(!strncasecmp("audio/", mime, 6)); + + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + msg->setInt32("channel-count", numChannels); + msg->setInt32("sample-rate", sampleRate); + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + // Parse the AVCDecoderConfigurationRecord + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + + // There is decodable content out there that fails the following + // assertion, let's be lenient for now... + // CHECK((ptr[4] >> 2) == 0x3f); // reserved + + size_t lengthSize = 1 + (ptr[4] & 3); + + // commented out check below as H264_QVGA_500_NO_AUDIO.3gp + // violates it... + // CHECK((ptr[5] >> 5) == 7); // reserved + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + sp<ABuffer> buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + buffer->meta()->setInt64("timeUs", 0); + + msg->setBuffer("csd-0", buffer); + + buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + CHECK(size >= 1); + size_t numPictureParameterSets = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPictureParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + buffer->meta()->setInt64("timeUs", 0); + msg->setBuffer("csd-1", buffer); + } else if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + sp<ABuffer> buffer = new ABuffer(codec_specific_data_size); + + memcpy(buffer->data(), codec_specific_data, + codec_specific_data_size); + + buffer->meta()->setInt32("csd", true); + buffer->meta()->setInt64("timeUs", 0); + msg->setBuffer("csd-0", buffer); + } else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) { + sp<ABuffer> buffer = new ABuffer(size); + memcpy(buffer->data(), data, size); + + buffer->meta()->setInt32("csd", true); + buffer->meta()->setInt64("timeUs", 0); + msg->setBuffer("csd-0", buffer); + + if (!meta->findData(kKeyVorbisBooks, &type, &data, &size)) { + return -EINVAL; + } + + buffer = new ABuffer(size); + memcpy(buffer->data(), data, size); + + buffer->meta()->setInt32("csd", true); + buffer->meta()->setInt64("timeUs", 0); + msg->setBuffer("csd-1", buffer); + } + + *format = msg; + + return OK; +} + +status_t NuMediaExtractor::selectTrack(size_t index) { + if (mImpl == NULL) { + return -EINVAL; + } + + if (index >= mImpl->countTracks()) { + return -ERANGE; + } + + for (size_t i = 0; i < mSelectedTracks.size(); ++i) { + TrackInfo *info = &mSelectedTracks.editItemAt(i); + + if (info->mTrackIndex == index) { + // This track has already been selected. + return OK; + } + } + + sp<MediaSource> source = mImpl->getTrack(index); + + CHECK_EQ((status_t)OK, source->start()); + + mSelectedTracks.push(); + TrackInfo *info = &mSelectedTracks.editItemAt(mSelectedTracks.size() - 1); + + info->mSource = source; + info->mTrackIndex = index; + info->mFinalResult = OK; + info->mSample = NULL; + info->mSampleTimeUs = -1ll; + info->mFlags = 0; + + const char *mime; + CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { + info->mFlags |= kIsVorbis; + } + + return OK; +} + +void NuMediaExtractor::releaseTrackSamples() { + for (size_t i = 0; i < mSelectedTracks.size(); ++i) { + TrackInfo *info = &mSelectedTracks.editItemAt(i); + + if (info->mSample != NULL) { + info->mSample->release(); + info->mSample = NULL; + + info->mSampleTimeUs = -1ll; + } + } +} + +ssize_t NuMediaExtractor::fetchTrackSamples(int64_t seekTimeUs) { + TrackInfo *minInfo = NULL; + ssize_t minIndex = -1; + + for (size_t i = 0; i < mSelectedTracks.size(); ++i) { + TrackInfo *info = &mSelectedTracks.editItemAt(i); + + if (seekTimeUs >= 0ll) { + info->mFinalResult = OK; + + if (info->mSample != NULL) { + info->mSample->release(); + info->mSample = NULL; + info->mSampleTimeUs = -1ll; + } + } else if (info->mFinalResult != OK) { + continue; + } + + if (info->mSample == NULL) { + MediaSource::ReadOptions options; + if (seekTimeUs >= 0ll) { + options.setSeekTo(seekTimeUs); + } + status_t err = info->mSource->read(&info->mSample, &options); + + if (err != OK) { + CHECK(info->mSample == NULL); + + info->mFinalResult = err; + info->mSampleTimeUs = -1ll; + continue; + } else { + CHECK(info->mSample != NULL); + CHECK(info->mSample->meta_data()->findInt64( + kKeyTime, &info->mSampleTimeUs)); + } + } + + if (minInfo == NULL || info->mSampleTimeUs < minInfo->mSampleTimeUs) { + minInfo = info; + minIndex = i; + } + } + + return minIndex; +} + +status_t NuMediaExtractor::seekTo(int64_t timeUs) { + return fetchTrackSamples(timeUs); +} + +status_t NuMediaExtractor::advance() { + ssize_t minIndex = fetchTrackSamples(); + + if (minIndex < 0) { + return ERROR_END_OF_STREAM; + } + + TrackInfo *info = &mSelectedTracks.editItemAt(minIndex); + + info->mSample->release(); + info->mSample = NULL; + info->mSampleTimeUs = -1ll; + + return OK; +} + +status_t NuMediaExtractor::readSampleData(const sp<ABuffer> &buffer) { + ssize_t minIndex = fetchTrackSamples(); + + if (minIndex < 0) { + return ERROR_END_OF_STREAM; + } + + TrackInfo *info = &mSelectedTracks.editItemAt(minIndex); + + size_t sampleSize = info->mSample->range_length(); + + if (info->mFlags & kIsVorbis) { + // Each sample's data is suffixed by the number of page samples + // or -1 if not available. + sampleSize += sizeof(int32_t); + } + + if (buffer->capacity() < sampleSize) { + return -ENOMEM; + } + + const uint8_t *src = + (const uint8_t *)info->mSample->data() + + info->mSample->range_offset(); + + memcpy((uint8_t *)buffer->data(), src, info->mSample->range_length()); + + if (info->mFlags & kIsVorbis) { + int32_t numPageSamples; + if (!info->mSample->meta_data()->findInt32( + kKeyValidSamples, &numPageSamples)) { + numPageSamples = -1; + } + + memcpy((uint8_t *)buffer->data() + info->mSample->range_length(), + &numPageSamples, + sizeof(numPageSamples)); + } + + buffer->setRange(0, sampleSize); + + return OK; +} + +status_t NuMediaExtractor::getSampleTrackIndex(size_t *trackIndex) { + ssize_t minIndex = fetchTrackSamples(); + + if (minIndex < 0) { + return ERROR_END_OF_STREAM; + } + + TrackInfo *info = &mSelectedTracks.editItemAt(minIndex); + *trackIndex = info->mTrackIndex; + + return OK; +} + +status_t NuMediaExtractor::getSampleTime(int64_t *sampleTimeUs) { + ssize_t minIndex = fetchTrackSamples(); + + if (minIndex < 0) { + return ERROR_END_OF_STREAM; + } + + TrackInfo *info = &mSelectedTracks.editItemAt(minIndex); + *sampleTimeUs = info->mSampleTimeUs; + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp index 7a805aa..7cdb793 100644 --- a/media/libstagefright/OMXClient.cpp +++ b/media/libstagefright/OMXClient.cpp @@ -335,6 +335,10 @@ status_t OMXClient::connect() { } void OMXClient::disconnect() { + if (mOMX.get() != NULL) { + mOMX.clear(); + mOMX = NULL; + } } } // namespace android diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 470f750..966416e 100755 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -33,6 +33,7 @@ #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXCodec.h> @@ -57,11 +58,6 @@ const static int64_t kBufferFilledEventTimeOutNs = 3000000000LL; // component in question is buggy or not. const static uint32_t kMaxColorFormatSupported = 1000; -struct CodecInfo { - const char *mime; - const char *codec; -}; - #define FACTORY_CREATE_ENCODER(name) \ static sp<MediaSource> Make##name(const sp<MediaSource> &source, const sp<MetaData> &meta) { \ return new name(source, meta); \ @@ -96,83 +92,8 @@ static sp<MediaSource> InstantiateSoftwareEncoder( return NULL; } +#undef FACTORY_CREATE_ENCODER #undef FACTORY_REF -#undef FACTORY_CREATE - -static const CodecInfo kDecoderInfo[] = { - { MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" }, -// { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" }, - { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.google.mp3.decoder" }, - { MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II, "OMX.Nvidia.mp2.decoder" }, -// { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.decode" }, -// { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amr.decoder" }, - { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.google.amrnb.decoder" }, -// { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amrwb.decoder" }, - { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.decode" }, - { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.google.amrwb.decoder" }, -// { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.Nvidia.aac.decoder" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.decoder" }, - { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "OMX.google.g711.alaw.decoder" }, - { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "OMX.google.g711.mlaw.decoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.DECODER" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.decode" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Decoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.google.mpeg4.decoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.DUCATI1.VIDEO.DECODER" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.decode" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.decoder.h263" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Decoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.google.h263.decoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.DUCATI1.VIDEO.DECODER" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.decode" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.SEC.AVC.Decoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.google.h264.decoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.google.avc.decoder" }, - { MEDIA_MIMETYPE_AUDIO_VORBIS, "OMX.google.vorbis.decoder" }, - { MEDIA_MIMETYPE_VIDEO_VPX, "OMX.google.vpx.decoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG2, "OMX.Nvidia.mpeg2v.decode" }, -}; - -static const CodecInfo kEncoderInfo[] = { - { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.encode" }, - { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.google.amrnb.encoder" }, - { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" }, - { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.google.amrwb.encoder" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.encoder" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "AACEncoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.MPEG4E" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.encoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.encoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Encoder" }, - { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Encoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.DUCATI1.VIDEO.MPEG4E" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.encoder.h263" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.encoder.h263" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.Video.encoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.encoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Encoder" }, - { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Encoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.DUCATI1.VIDEO.H264E" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.encoder.avc" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.encoder.avc" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.encoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.encoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.SEC.AVC.Encoder" }, - { MEDIA_MIMETYPE_VIDEO_AVC, "AVCEncoder" }, -}; - -#undef OPTIONAL #define CODEC_LOGI(x, ...) ALOGI("[%s] "x, mComponentName, ##__VA_ARGS__) #define CODEC_LOGV(x, ...) ALOGV("[%s] "x, mComponentName, ##__VA_ARGS__) @@ -207,22 +128,6 @@ private: OMXCodecObserver &operator=(const OMXCodecObserver &); }; -static const char *GetCodec(const CodecInfo *info, size_t numInfos, - const char *mime, int index) { - CHECK(index >= 0); - for(size_t i = 0; i < numInfos; ++i) { - if (!strcasecmp(mime, info[i].mime)) { - if (index == 0) { - return info[i].codec; - } - - --index; - } - } - - return NULL; -} - template<class T> static void InitOMXParams(T *params) { params->nSize = sizeof(T); @@ -278,119 +183,36 @@ static int CompareSoftwareCodecsFirst( } // static -uint32_t OMXCodec::getComponentQuirks( - const char *componentName, bool isEncoder) { - uint32_t quirks = 0; - - if (!strcmp(componentName, "OMX.Nvidia.amr.decoder") || - !strcmp(componentName, "OMX.Nvidia.amrwb.decoder") || - !strcmp(componentName, "OMX.Nvidia.aac.decoder") || - !strcmp(componentName, "OMX.Nvidia.mp3.decoder")) { - quirks |= kDecoderLiesAboutNumberOfChannels; - } - - if (!strcmp(componentName, "OMX.TI.MP3.decode")) { - quirks |= kNeedsFlushBeforeDisable; - quirks |= kDecoderLiesAboutNumberOfChannels; - } - if (!strcmp(componentName, "OMX.TI.AAC.decode")) { - quirks |= kNeedsFlushBeforeDisable; - quirks |= kRequiresFlushCompleteEmulation; - quirks |= kSupportsMultipleFramesPerInputBuffer; - } - if (!strncmp(componentName, "OMX.qcom.video.encoder.", 23)) { - quirks |= kRequiresLoadedToIdleAfterAllocation; - quirks |= kRequiresAllocateBufferOnInputPorts; - quirks |= kRequiresAllocateBufferOnOutputPorts; - if (!strncmp(componentName, "OMX.qcom.video.encoder.avc", 26)) { - - // The AVC encoder advertises the size of output buffers - // based on the input video resolution and assumes - // the worst/least compression ratio is 0.5. It is found that - // sometimes, the output buffer size is larger than - // size advertised by the encoder. - quirks |= kRequiresLargerEncoderOutputBuffer; - } - } - if (!strncmp(componentName, "OMX.qcom.7x30.video.encoder.", 28)) { - } - if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) { - quirks |= kRequiresAllocateBufferOnOutputPorts; - quirks |= kDefersOutputBufferAllocation; - } - if (!strncmp(componentName, "OMX.qcom.7x30.video.decoder.", 28)) { - quirks |= kRequiresAllocateBufferOnInputPorts; - quirks |= kRequiresAllocateBufferOnOutputPorts; - quirks |= kDefersOutputBufferAllocation; - } - - if (!strcmp(componentName, "OMX.TI.DUCATI1.VIDEO.DECODER")) { - quirks |= kRequiresAllocateBufferOnInputPorts; - quirks |= kRequiresAllocateBufferOnOutputPorts; - } - - // FIXME: - // Remove the quirks after the work is done. - else if (!strcmp(componentName, "OMX.TI.DUCATI1.VIDEO.MPEG4E") || - !strcmp(componentName, "OMX.TI.DUCATI1.VIDEO.H264E")) { - - quirks |= kRequiresAllocateBufferOnInputPorts; - quirks |= kRequiresAllocateBufferOnOutputPorts; - } - else if (!strncmp(componentName, "OMX.TI.", 7)) { - // Apparently I must not use OMX_UseBuffer on either input or - // output ports on any of the TI components or quote: - // "(I) may have unexpected problem (sic) which can be timing related - // and hard to reproduce." - - quirks |= kRequiresAllocateBufferOnInputPorts; - quirks |= kRequiresAllocateBufferOnOutputPorts; - if (!strncmp(componentName, "OMX.TI.Video.encoder", 20)) { - quirks |= kAvoidMemcopyInputRecordingFrames; - } - } - - if (!strcmp(componentName, "OMX.TI.Video.Decoder")) { - quirks |= kInputBufferSizesAreBogus; - } - - if (!strncmp(componentName, "OMX.SEC.", 8) && !isEncoder) { - // These output buffers contain no video data, just some - // opaque information that allows the overlay to display their - // contents. - quirks |= kOutputBuffersAreUnreadable; - } - - return quirks; -} - -// static void OMXCodec::findMatchingCodecs( const char *mime, bool createEncoder, const char *matchComponentName, uint32_t flags, - Vector<String8> *matchingCodecs) { + Vector<String8> *matchingCodecs, + Vector<uint32_t> *matchingCodecQuirks) { matchingCodecs->clear(); - for (int index = 0;; ++index) { - const char *componentName; + if (matchingCodecQuirks) { + matchingCodecQuirks->clear(); + } - if (createEncoder) { - componentName = GetCodec( - kEncoderInfo, - sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), - mime, index); - } else { - componentName = GetCodec( - kDecoderInfo, - sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]), - mime, index); - } + const MediaCodecList *list = MediaCodecList::getInstance(); + if (list == NULL) { + return; + } + + size_t index = 0; + for (;;) { + ssize_t matchIndex = + list->findCodecByType(mime, createEncoder, index); - if (!componentName) { + if (matchIndex < 0) { break; } + index = matchIndex + 1; + + const char *componentName = list->getCodecName(matchIndex); + // If a specific codec is requested, skip the non-matching ones. if (matchComponentName && strcmp(componentName, matchComponentName)) { continue; @@ -405,6 +227,10 @@ void OMXCodec::findMatchingCodecs( (!(flags & (kSoftwareCodecsOnly | kHardwareCodecsOnly)))) { matchingCodecs->push(String8(componentName)); + + if (matchingCodecQuirks) { + matchingCodecQuirks->push(getComponentQuirks(list, matchIndex)); + } } } @@ -414,6 +240,45 @@ void OMXCodec::findMatchingCodecs( } // static +uint32_t OMXCodec::getComponentQuirks( + const MediaCodecList *list, size_t index) { + uint32_t quirks = 0; + if (list->codecHasQuirk( + index, "requires-allocate-on-input-ports")) { + quirks |= kRequiresAllocateBufferOnInputPorts; + } + if (list->codecHasQuirk( + index, "requires-allocate-on-output-ports")) { + quirks |= kRequiresAllocateBufferOnOutputPorts; + } + if (list->codecHasQuirk( + index, "output-buffers-are-unreadable")) { + quirks |= kOutputBuffersAreUnreadable; + } + + return quirks; +} + +// static +bool OMXCodec::findCodecQuirks(const char *componentName, uint32_t *quirks) { + const MediaCodecList *list = MediaCodecList::getInstance(); + + if (list == NULL) { + return false; + } + + ssize_t index = list->findCodecByName(componentName); + + if (index < 0) { + return false; + } + + *quirks = getComponentQuirks(list, index); + + return true; +} + +// static sp<MediaSource> OMXCodec::Create( const sp<IOMX> &omx, const sp<MetaData> &meta, bool createEncoder, @@ -435,8 +300,10 @@ sp<MediaSource> OMXCodec::Create( CHECK(success); Vector<String8> matchingCodecs; + Vector<uint32_t> matchingCodecQuirks; findMatchingCodecs( - mime, createEncoder, matchComponentName, flags, &matchingCodecs); + mime, createEncoder, matchComponentName, flags, + &matchingCodecs, &matchingCodecQuirks); if (matchingCodecs.isEmpty()) { return NULL; @@ -447,6 +314,7 @@ sp<MediaSource> OMXCodec::Create( for (size_t i = 0; i < matchingCodecs.size(); ++i) { const char *componentNameBase = matchingCodecs[i].string(); + uint32_t quirks = matchingCodecQuirks[i]; const char *componentName = componentNameBase; AString tmp; @@ -470,8 +338,6 @@ sp<MediaSource> OMXCodec::Create( ALOGV("Attempting to allocate OMX node '%s'", componentName); - uint32_t quirks = getComponentQuirks(componentNameBase, createEncoder); - if (!createEncoder && (quirks & kOutputBuffersAreUnreadable) && (flags & kClientNeedsFramebuffer)) { @@ -627,16 +493,6 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { CODEC_LOGI( "AVC profile = %u (%s), level = %u", profile, AVCProfileToString(profile), level); - - if (!strcmp(mComponentName, "OMX.TI.Video.Decoder") - && (profile != kAVCProfileBaseline || level > 30)) { - // This stream exceeds the decoder's capabilities. The decoder - // does not handle this gracefully and would clobber the heap - // and wreak havoc instead... - - ALOGE("Profile and/or level exceed the decoder's capabilities."); - return ERROR_UNSUPPORTED; - } } else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) { addCodecSpecificData(data, size); @@ -692,40 +548,11 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { } } - if (!strcasecmp(mMIME, MEDIA_MIMETYPE_IMAGE_JPEG) - && !strcmp(mComponentName, "OMX.TI.JPEG.decode")) { - OMX_COLOR_FORMATTYPE format = - OMX_COLOR_Format32bitARGB8888; - // OMX_COLOR_FormatYUV420PackedPlanar; - // OMX_COLOR_FormatCbYCrY; - // OMX_COLOR_FormatYUV411Planar; - - int32_t width, height; - bool success = meta->findInt32(kKeyWidth, &width); - success = success && meta->findInt32(kKeyHeight, &height); - - int32_t compressedSize; - success = success && meta->findInt32( - kKeyMaxInputSize, &compressedSize); - - CHECK(success); - CHECK(compressedSize > 0); - - setImageOutputFormat(format, width, height); - setJPEGInputFormat(width, height, (OMX_U32)compressedSize); - } - int32_t maxInputSize; if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { setMinBufferSize(kPortIndexInput, (OMX_U32)maxInputSize); } - if (!strcmp(mComponentName, "OMX.TI.AMR.encode") - || !strcmp(mComponentName, "OMX.TI.WBAMR.encode") - || !strcmp(mComponentName, "OMX.TI.AAC.encode")) { - setMinBufferSize(kPortIndexOutput, 8192); // XXX - } - initOutputFormat(meta); if ((mFlags & kClientNeedsFramebuffer) @@ -829,21 +656,6 @@ status_t OMXCodec::setVideoPortFormatType( index, format.eCompressionFormat, format.eColorFormat); #endif - if (!strcmp("OMX.TI.Video.encoder", mComponentName)) { - if (portIndex == kPortIndexInput - && colorFormat == format.eColorFormat) { - // eCompressionFormat does not seem right. - found = true; - break; - } - if (portIndex == kPortIndexOutput - && compressionFormat == format.eCompressionFormat) { - // eColorFormat does not seem right. - found = true; - break; - } - } - if (format.eCompressionFormat == compressionFormat && format.eColorFormat == colorFormat) { found = true; @@ -906,13 +718,8 @@ status_t OMXCodec::findTargetColorFormat( int32_t targetColorFormat; if (meta->findInt32(kKeyColorFormat, &targetColorFormat)) { *colorFormat = (OMX_COLOR_FORMATTYPE) targetColorFormat; - } else { - if (!strcasecmp("OMX.TI.Video.encoder", mComponentName)) { - *colorFormat = OMX_COLOR_FormatYCbYCr; - } } - // Check whether the target color format is supported. return isColorFormatSupported(*colorFormat, kPortIndexInput); } @@ -1541,6 +1348,8 @@ void OMXCodec::setComponentRole( "video_decoder.mpeg4", "video_encoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_H263, "video_decoder.h263", "video_encoder.h263" }, + { MEDIA_MIMETYPE_VIDEO_VPX, + "video_decoder.vpx", "video_encoder.vpx" }, }; static const size_t kNumMimeToRole = @@ -3324,13 +3133,6 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { info->mStatus = OWNED_BY_COMPONENT; - // This component does not ever signal the EOS flag on output buffers, - // Thanks for nothing. - if (mSignalledEOS && !strcmp(mComponentName, "OMX.TI.Video.encoder")) { - mNoMoreOutputData = true; - mBufferFilled.signal(); - } - return true; } @@ -3556,6 +3358,7 @@ status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t //////////////// output port //////////////////// // format OMX_AUDIO_PARAM_PORTFORMATTYPE format; + InitOMXParams(&format); format.nPortIndex = kPortIndexOutput; format.nIndex = 0; status_t err = OMX_ErrorNone; diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp index aa047d6..ab2cff0 100644 --- a/media/libstagefright/SurfaceMediaSource.cpp +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -24,9 +24,8 @@ #include <media/stagefright/MetadataBufferType.h> #include <ui/GraphicBuffer.h> -#include <surfaceflinger/ISurfaceComposer.h> -#include <surfaceflinger/SurfaceComposerClient.h> -#include <surfaceflinger/IGraphicBufferAlloc.h> +#include <gui/ISurfaceComposer.h> +#include <gui/IGraphicBufferAlloc.h> #include <OMX_Component.h> #include <utils/Log.h> diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp index f4b5d4f..6d345bb 100644 --- a/media/libstagefright/TimedEventQueue.cpp +++ b/media/libstagefright/TimedEventQueue.cpp @@ -26,8 +26,6 @@ #include "include/TimedEventQueue.h" -#include <cutils/sched_policy.h> - #include <sys/prctl.h> #include <sys/time.h> diff --git a/media/libstagefright/codecs/aacenc/Android.mk b/media/libstagefright/codecs/aacenc/Android.mk index 34a2796..509193c 100644 --- a/media/libstagefright/codecs/aacenc/Android.mk +++ b/media/libstagefright/codecs/aacenc/Android.mk @@ -79,7 +79,7 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/asm/ARMV5E endif ifeq ($(VOTT), v7) -LOCAL_CFLAGS += -DARMV5E -DARMV7Neon -DARM_INASM -DARMV5_INASM +LOCAL_CFLAGS += -DARMV5E -DARMV7Neon -DARM_INASM -DARMV5_INASM -DARMV6_INASM LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/asm/ARMV5E LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/asm/ARMV7 endif diff --git a/media/libstagefright/codecs/aacenc/basic_op/basic_op.h b/media/libstagefright/codecs/aacenc/basic_op/basic_op.h index ef3c31b..5cd7e5f 100644 --- a/media/libstagefright/codecs/aacenc/basic_op/basic_op.h +++ b/media/libstagefright/codecs/aacenc/basic_op/basic_op.h @@ -227,27 +227,18 @@ Word32 L_shr_r (Word32 L_var1, Word16 var2); #if ARMV4_INASM __inline Word32 ASM_L_shr(Word32 L_var1, Word16 var2) { - Word32 result; - asm volatile( - "MOV %[result], %[L_var1], ASR %[var2] \n" - :[result]"=r"(result) - :[L_var1]"r"(L_var1), [var2]"r"(var2) - ); - return result; + return L_var1 >> var2; } __inline Word32 ASM_L_shl(Word32 L_var1, Word16 var2) { Word32 result; - asm volatile( - "MOV r2, %[L_var1] \n" - "MOV r3, #0x7fffffff\n" + asm ( "MOV %[result], %[L_var1], ASL %[var2] \n" - "TEQ r2, %[result], ASR %[var2]\n" - "EORNE %[result],r3,r2,ASR#31\n" - :[result]"+r"(result) - :[L_var1]"r"(L_var1), [var2]"r"(var2) - :"r2", "r3" + "TEQ %[L_var1], %[result], ASR %[var2]\n" + "EORNE %[result], %[mask], %[L_var1], ASR #31\n" + :[result]"=&r"(result) + :[L_var1]"r"(L_var1), [var2]"r"(var2), [mask]"r"(0x7fffffff) ); return result; } @@ -255,10 +246,10 @@ __inline Word32 ASM_L_shl(Word32 L_var1, Word16 var2) __inline Word32 ASM_shr(Word32 L_var1, Word16 var2) { Word32 result; - asm volatile( + asm ( "CMP %[var2], #15\n" - "MOVGE %[var2], #15\n" - "MOV %[result], %[L_var1], ASR %[var2]\n" + "MOVLT %[result], %[L_var1], ASR %[var2]\n" + "MOVGE %[result], %[L_var1], ASR #15\n" :[result]"=r"(result) :[L_var1]"r"(L_var1), [var2]"r"(var2) ); @@ -267,21 +258,32 @@ __inline Word32 ASM_shr(Word32 L_var1, Word16 var2) __inline Word32 ASM_shl(Word32 L_var1, Word16 var2) { +#if ARMV6_SAT Word32 result; - asm volatile( + asm ( "CMP %[var2], #16\n" - "MOVGE %[var2], #16\n" - "MOV %[result], %[L_var1], ASL %[var2]\n" - "MOV r3, #1\n" - "MOV r2, %[result], ASR #15\n" - "RSB r3,r3,r3,LSL #15 \n" - "TEQ r2, %[result], ASR #31 \n" - "EORNE %[result], r3, %[result],ASR #31" - :[result]"+r"(result) + "MOVLT %[result], %[L_var1], ASL %[var2]\n" + "MOVGE %[result], %[L_var1], ASL #16\n" + "SSAT %[result], #16, %[result]\n" + :[result]"=r"(result) :[L_var1]"r"(L_var1), [var2]"r"(var2) - :"r2", "r3" ); return result; +#else + Word32 result; + Word32 tmp; + asm ( + "CMP %[var2], #16\n" + "MOVLT %[result], %[L_var1], ASL %[var2]\n" + "MOVGE %[result], %[L_var1], ASL #16\n" + "MOV %[tmp], %[result], ASR #15\n" + "TEQ %[tmp], %[result], ASR #31 \n" + "EORNE %[result], %[mask], %[result],ASR #31" + :[result]"=&r"(result), [tmp]"=&r"(tmp) + :[L_var1]"r"(L_var1), [var2]"r"(var2), [mask]"r"(0x7fff) + ); + return result; +#endif } #endif @@ -293,18 +295,24 @@ __inline Word32 ASM_shl(Word32 L_var1, Word16 var2) #if (SATRUATE_IS_INLINE) __inline Word16 saturate(Word32 L_var1) { -#if ARMV5TE_SAT +#if ARMV6_SAT + Word16 result; + asm ( + "SSAT %[result], #16, %[L_var1]" + : [result]"=r"(result) + : [L_var1]"r"(L_var1) + ); + return result; +#elif ARMV5TE_SAT Word16 result; + Word32 tmp; asm volatile ( - "MOV %[result], %[L_var1]\n" - "MOV r3, #1\n" - "MOV r2,%[L_var1],ASR#15\n" - "RSB r3, r3, r3, LSL #15\n" - "TEQ r2,%[L_var1],ASR#31\n" - "EORNE %[result],r3,%[L_var1],ASR#31\n" - :[result]"+r"(result) - :[L_var1]"r"(L_var1) - :"r2", "r3" + "MOV %[tmp], %[L_var1],ASR#15\n" + "TEQ %[tmp], %[L_var1],ASR#31\n" + "EORNE %[result], %[mask],%[L_var1],ASR#31\n" + "MOVEQ %[result], %[L_var1]\n" + :[result]"=&r"(result), [tmp]"=&r"(tmp) + :[L_var1]"r"(L_var1), [mask]"r"(0x7fff) ); return result; @@ -420,10 +428,10 @@ __inline Word32 L_mult(Word16 var1, Word16 var2) { #if ARMV5TE_L_MULT Word32 result; - asm volatile( + asm ( "SMULBB %[result], %[var1], %[var2] \n" "QADD %[result], %[result], %[result] \n" - :[result]"+r"(result) + :[result]"=r"(result) :[var1]"r"(var1), [var2]"r"(var2) ); return result; @@ -450,11 +458,10 @@ __inline Word32 L_msu (Word32 L_var3, Word16 var1, Word16 var2) { #if ARMV5TE_L_MSU Word32 result; - asm volatile( + asm ( "SMULBB %[result], %[var1], %[var2] \n" - "QADD %[result], %[result], %[result] \n" - "QSUB %[result], %[L_var3], %[result]\n" - :[result]"+r"(result) + "QDSUB %[result], %[L_var3], %[result]\n" + :[result]"=&r"(result) :[L_var3]"r"(L_var3), [var1]"r"(var1), [var2]"r"(var2) ); return result; @@ -474,9 +481,9 @@ __inline Word32 L_sub(Word32 L_var1, Word32 L_var2) { #if ARMV5TE_L_SUB Word32 result; - asm volatile( + asm ( "QSUB %[result], %[L_var1], %[L_var2]\n" - :[result]"+r"(result) + :[result]"=r"(result) :[L_var1]"r"(L_var1), [L_var2]"r"(L_var2) ); return result; @@ -589,16 +596,14 @@ __inline Word16 add (Word16 var1, Word16 var2) { #if ARMV5TE_ADD Word32 result; - asm volatile( + Word32 tmp; + asm ( "ADD %[result], %[var1], %[var2] \n" - "MOV r3, #0x1\n" - "MOV r2, %[result], ASR #15\n" - "RSB r3, r3, r3, LSL, #15\n" - "TEQ r2, %[result], ASR #31\n" - "EORNE %[result], r3, %[result], ASR #31" - :[result]"+r"(result) - :[var1]"r"(var1), [var2]"r"(var2) - :"r2", "r3" + "MOV %[tmp], %[result], ASR #15 \n" + "TEQ %[tmp], %[result], ASR #31 \n" + "EORNE %[result], %[mask], %[result], ASR #31" + :[result]"=&r"(result), [tmp]"=&r"(tmp) + :[var1]"r"(var1), [var2]"r"(var2), [mask]"r"(0x7fff) ); return result; #else @@ -619,16 +624,14 @@ __inline Word16 sub(Word16 var1, Word16 var2) { #if ARMV5TE_SUB Word32 result; - asm volatile( - "MOV r3, #1\n" + Word32 tmp; + asm ( "SUB %[result], %[var1], %[var2] \n" - "RSB r3,r3,r3,LSL#15\n" - "MOV r2, %[var1], ASR #15 \n" - "TEQ r2, %[var1], ASR #31 \n" - "EORNE %[result], r3, %[result], ASR #31 \n" - :[result]"+r"(result) - :[var1]"r"(var1), [var2]"r"(var2) - :"r2", "r3" + "MOV %[tmp], %[var1], ASR #15 \n" + "TEQ %[tmp], %[var1], ASR #31 \n" + "EORNE %[result], %[mask], %[result], ASR #31 \n" + :[result]"=&r"(result), [tmp]"=&r"(tmp) + :[var1]"r"(var1), [var2]"r"(var2), [mask]"r"(0x7fff) ); return result; #else @@ -682,19 +685,25 @@ __inline Word16 div_s (Word16 var1, Word16 var2) #if (MULT_IS_INLINE) __inline Word16 mult (Word16 var1, Word16 var2) { -#if ARMV5TE_MULT +#if ARMV5TE_MULT && ARMV6_SAT Word32 result; - asm volatile( - "SMULBB r2, %[var1], %[var2] \n" - "MOV r3, #1\n" - "MOV %[result], r2, ASR #15\n" - "RSB r3, r3, r3, LSL #15\n" - "MOV r2, %[result], ASR #15\n" - "TEQ r2, %[result], ASR #31\n" - "EORNE %[result], r3, %[result], ASR #31 \n" - :[result]"+r"(result) + asm ( + "SMULBB %[result], %[var1], %[var2] \n" + "SSAT %[result], #16, %[result], ASR #15 \n" + :[result]"=r"(result) :[var1]"r"(var1), [var2]"r"(var2) - :"r2", "r3" + ); + return result; +#elif ARMV5TE_MULT + Word32 result, tmp; + asm ( + "SMULBB %[tmp], %[var1], %[var2] \n" + "MOV %[result], %[tmp], ASR #15\n" + "MOV %[tmp], %[result], ASR #15\n" + "TEQ %[tmp], %[result], ASR #31\n" + "EORNE %[result], %[mask], %[result], ASR #31 \n" + :[result]"=&r"(result), [tmp]"=&r"(tmp) + :[var1]"r"(var1), [var2]"r"(var2), [mask]"r"(0x7fff) ); return result; #else @@ -719,18 +728,17 @@ __inline Word16 norm_s (Word16 var1) { #if ARMV5TE_NORM_S Word16 result; - asm volatile( - "MOV r2,%[var1] \n" - "CMP r2, #0\n" - "RSBLT %[var1], %[var1], #0 \n" - "CLZNE %[result], %[var1]\n" + Word32 tmp; + asm ( + "RSBS %[tmp], %[var1], #0 \n" + "CLZLT %[result], %[var1]\n" + "CLZGT %[result], %[tmp]\n" "SUBNE %[result], %[result], #17\n" "MOVEQ %[result], #0\n" - "CMP r2, #-1\n" + "CMP %[var1], #-1\n" "MOVEQ %[result], #15\n" - :[result]"+r"(result) + :[result]"=&r"(result), [tmp]"=&r"(tmp) :[var1]"r"(var1) - :"r2" ); return result; #else @@ -774,7 +782,7 @@ __inline Word16 norm_l (Word32 L_var1) "CLZNE %[result], %[L_var1]\n" "SUBNE %[result], %[result], #1\n" "MOVEQ %[result], #0\n" - :[result]"+r"(result) + :[result]"=r"(result) :[L_var1]"r"(L_var1) ); return result; @@ -979,13 +987,11 @@ __inline Word16 round16(Word32 L_var1) { #if ARMV5TE_ROUND Word16 result; - asm volatile( - "MOV r1,#0x00008000\n" - "QADD %[result], %[L_var1], r1\n" + asm ( + "QADD %[result], %[L_var1], %[bias]\n" "MOV %[result], %[result], ASR #16 \n" - :[result]"+r"(result) - :[L_var1]"r"(L_var1) - :"r1" + :[result]"=r"(result) + :[L_var1]"r"(L_var1), [bias]"r"(0x8000) ); return result; #else @@ -1005,11 +1011,10 @@ __inline Word32 L_mac (Word32 L_var3, Word16 var1, Word16 var2) { #if ARMV5TE_L_MAC Word32 result; - asm volatile( + asm ( "SMULBB %[result], %[var1], %[var2]\n" - "QADD %[result], %[result], %[result]\n" - "QADD %[result], %[result], %[L_var3]\n" - :[result]"+r"(result) + "QDADD %[result], %[L_var3], %[result]\n" + :[result]"=&r"(result) : [L_var3]"r"(L_var3), [var1]"r"(var1), [var2]"r"(var2) ); return result; @@ -1029,9 +1034,9 @@ __inline Word32 L_add (Word32 L_var1, Word32 L_var2) { #if ARMV5TE_L_ADD Word32 result; - asm volatile( + asm ( "QADD %[result], %[L_var1], %[L_var2]\n" - :[result]"+r"(result) + :[result]"=r"(result) :[L_var1]"r"(L_var1), [L_var2]"r"(L_var2) ); return result; diff --git a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.h b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.h index 9ebd1c2..6e5844f 100644 --- a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.h +++ b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.h @@ -63,7 +63,7 @@ __inline Word32 L_mpy_wx(Word32 L_var2, Word16 var1) Word32 result; asm volatile( "SMULWB %[result], %[L_var2], %[var1] \n" - :[result]"+r"(result) + :[result]"=r"(result) :[L_var2]"r"(L_var2), [var1]"r"(var1) ); return result; diff --git a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h index 2d5d956..6059237 100644 --- a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h +++ b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h @@ -48,9 +48,7 @@ #define assert(_Expression) ((void)0) #endif -#ifdef LINUX -#define __inline static __inline__ -#endif +#define __inline static __inline #define INT_BITS 32 /* @@ -130,6 +128,13 @@ typedef unsigned __int64 UWord64; #define ARMV5TE_NORM_L 1 #define ARMV5TE_L_MPY_LS 1 #endif +#if ARMV6_INASM + #undef ARMV5TE_ADD + #define ARMV5TE_ADD 0 + #undef ARMV5TE_SUB + #define ARMV5TE_SUB 0 + #define ARMV6_SAT 1 +#endif //basic operation functions optimization flags #define SATRUATE_IS_INLINE 1 //define saturate as inline function diff --git a/media/libstagefright/codecs/aacenc/inc/aacenc_core.h b/media/libstagefright/codecs/aacenc/inc/aacenc_core.h index 1acdbbc..bb75b6d 100644 --- a/media/libstagefright/codecs/aacenc/inc/aacenc_core.h +++ b/media/libstagefright/codecs/aacenc/inc/aacenc_core.h @@ -102,7 +102,7 @@ Word16 AacEncEncode(AAC_ENCODER *hAacEnc, const UWord8 *ancBytes, /*!< pointer to ancillary data bytes */ Word16 *numAncBytes, /*!< number of ancillary Data Bytes, send as fill element */ UWord8 *outBytes, /*!< pointer to output buffer */ - Word32 *numOutBytes /*!< number of bytes in output buffer */ + VO_U32 *numOutBytes /*!< number of bytes in output buffer */ ); /*--------------------------------------------------------------------------- diff --git a/media/libstagefright/codecs/aacenc/inc/bitbuffer.h b/media/libstagefright/codecs/aacenc/inc/bitbuffer.h index e538064..7c79f07 100644 --- a/media/libstagefright/codecs/aacenc/inc/bitbuffer.h +++ b/media/libstagefright/codecs/aacenc/inc/bitbuffer.h @@ -76,7 +76,7 @@ Word16 GetBitsAvail(HANDLE_BIT_BUF hBitBuf); Word16 WriteBits(HANDLE_BIT_BUF hBitBuf, - Word32 writeValue, + UWord32 writeValue, Word16 noBitsToWrite); void ResetBitBuf(HANDLE_BIT_BUF hBitBuf, diff --git a/media/libstagefright/codecs/aacenc/inc/psy_configuration.h b/media/libstagefright/codecs/aacenc/inc/psy_configuration.h index 9abfc99..f6981fa 100644 --- a/media/libstagefright/codecs/aacenc/inc/psy_configuration.h +++ b/media/libstagefright/codecs/aacenc/inc/psy_configuration.h @@ -31,7 +31,7 @@ typedef struct{ Word16 sfbCnt; Word16 sfbActive; /* number of sf bands containing energy after lowpass */ - Word16 *sfbOffset; + const Word16 *sfbOffset; Word32 sfbThresholdQuiet[MAX_SFB_LONG]; @@ -61,7 +61,7 @@ typedef struct{ Word16 sfbCnt; Word16 sfbActive; /* number of sf bands containing energy after lowpass */ - Word16 *sfbOffset; + const Word16 *sfbOffset; Word32 sfbThresholdQuiet[MAX_SFB_SHORT]; diff --git a/media/libstagefright/codecs/aacenc/src/aacenc_core.c b/media/libstagefright/codecs/aacenc/src/aacenc_core.c index 2b3bd48..cecbc8f 100644 --- a/media/libstagefright/codecs/aacenc/src/aacenc_core.c +++ b/media/libstagefright/codecs/aacenc/src/aacenc_core.c @@ -146,7 +146,7 @@ Word16 AacEncEncode(AAC_ENCODER *aacEnc, /*!< an encoder handle */ const UWord8 *ancBytes, /*!< pointer to ancillary data bytes */ Word16 *numAncBytes, /*!< number of ancillary Data Bytes */ UWord8 *outBytes, /*!< pointer to output buffer (must be large MINBITS_COEF/8*MAX_CHANNELS bytes) */ - Word32 *numOutBytes /*!< number of bytes in output buffer after processing */ + VO_U32 *numOutBytes /*!< number of bytes in output buffer after processing */ ) { ELEMENT_INFO *elInfo = &aacEnc->elInfo; diff --git a/media/libstagefright/codecs/aacenc/src/adj_thr.c b/media/libstagefright/codecs/aacenc/src/adj_thr.c index a8ab809..373b063 100644 --- a/media/libstagefright/codecs/aacenc/src/adj_thr.c +++ b/media/libstagefright/codecs/aacenc/src/adj_thr.c @@ -26,6 +26,7 @@ #include "adj_thr.h" #include "qc_data.h" #include "line_pe.h" +#include <string.h> #define minSnrLimit 0x6666 /* 1 dB */ @@ -1138,6 +1139,7 @@ void AdjustThresholds(ADJ_THR_STATE *adjThrState, Word16 maxBitresBits = elBits->maxBits; Word16 sideInfoBits = (qcOE->staticBitsUsed + qcOE->ancBitsUsed); Word16 ch; + memset(&peData, 0, sizeof(peData)); prepareSfbPe(&peData, psyOutChannel, logSfbEnergy, sfbNRelevantLines, nChannels, AdjThrStateElement->peOffset); diff --git a/media/libstagefright/codecs/aacenc/src/bitbuffer.c b/media/libstagefright/codecs/aacenc/src/bitbuffer.c index 5615ac3..0ce93d3 100644 --- a/media/libstagefright/codecs/aacenc/src/bitbuffer.c +++ b/media/libstagefright/codecs/aacenc/src/bitbuffer.c @@ -138,7 +138,7 @@ Word16 GetBitsAvail(HANDLE_BIT_BUF hBitBuf) * *****************************************************************************/ Word16 WriteBits(HANDLE_BIT_BUF hBitBuf, - Word32 writeValue, + UWord32 writeValue, Word16 noBitsToWrite) { Word16 wBitPos; @@ -152,6 +152,7 @@ Word16 WriteBits(HANDLE_BIT_BUF hBitBuf, wBitPos = hBitBuf->wBitPos; wBitPos += noBitsToWrite; + writeValue &= ~(0xffffffff << noBitsToWrite); // Mask out everything except the lowest noBitsToWrite bits writeValue <<= 32 - wBitPos; writeValue |= hBitBuf->cache; diff --git a/media/libstagefright/codecs/aacenc/src/dyn_bits.c b/media/libstagefright/codecs/aacenc/src/dyn_bits.c index 3d2efdc..7769188 100644 --- a/media/libstagefright/codecs/aacenc/src/dyn_bits.c +++ b/media/libstagefright/codecs/aacenc/src/dyn_bits.c @@ -281,7 +281,7 @@ noiselessCounter(SECTION_DATA *sectionData, const Word32 blockType) { Word32 grpNdx, i; - Word16 *sideInfoTab = NULL; + const Word16 *sideInfoTab = NULL; SECTION_INFO *sectionInfo; /* diff --git a/media/libstagefright/codecs/aacenc/src/interface.c b/media/libstagefright/codecs/aacenc/src/interface.c index f2472d8..d0ad433 100644 --- a/media/libstagefright/codecs/aacenc/src/interface.c +++ b/media/libstagefright/codecs/aacenc/src/interface.c @@ -99,8 +99,8 @@ void BuildInterface(Word32 *groupedMdctSpectrum, Word32 i; Word32 accuSumMS=0; Word32 accuSumLR=0; - Word32 *pSumMS = sfbEnergySumMS.sfbShort; - Word32 *pSumLR = sfbEnergySumLR.sfbShort; + const Word32 *pSumMS = sfbEnergySumMS.sfbShort; + const Word32 *pSumLR = sfbEnergySumLR.sfbShort; for (i=TRANS_FAC; i; i--) { accuSumLR = L_add(accuSumLR, *pSumLR); pSumLR++; diff --git a/media/libstagefright/codecs/aacenc/src/psy_configuration.c b/media/libstagefright/codecs/aacenc/src/psy_configuration.c index 02d92ab..dd40f9b 100644 --- a/media/libstagefright/codecs/aacenc/src/psy_configuration.c +++ b/media/libstagefright/codecs/aacenc/src/psy_configuration.c @@ -139,7 +139,7 @@ static Word16 BarcLineValue(Word16 noOfLines, Word16 fftLine, Word32 samplingFre * *****************************************************************************/ static void initThrQuiet(Word16 numPb, - Word16 *pbOffset, + const Word16 *pbOffset, Word16 *pbBarcVal, Word32 *pbThresholdQuiet) { Word16 i; @@ -250,7 +250,7 @@ static void initSpreading(Word16 numPb, * *****************************************************************************/ static void initBarcValues(Word16 numPb, - Word16 *pbOffset, + const Word16 *pbOffset, Word16 numLines, Word32 samplingFrequency, Word16 *pbBval) diff --git a/media/libstagefright/codecs/aacenc/src/psy_main.c b/media/libstagefright/codecs/aacenc/src/psy_main.c index 085acb8..4e9218c 100644 --- a/media/libstagefright/codecs/aacenc/src/psy_main.c +++ b/media/libstagefright/codecs/aacenc/src/psy_main.c @@ -658,7 +658,8 @@ static Word16 advancePsychShort(PSY_DATA* psyData, Word32 normEnergyShift = (psyData->mdctScale + 1) << 1; /* in reference code, mdct spectrum must be multipied with 2, so +1 */ Word32 clipEnergy = hPsyConfShort->clipEnergy >> normEnergyShift; Word32 wOffset = 0; - Word32 *data0, *data1; + Word32 *data0; + const Word32 *data1; for(w = 0; w < TRANS_FAC; w++) { Word32 i, tdata; diff --git a/media/libstagefright/codecs/aacenc/src/qc_main.c b/media/libstagefright/codecs/aacenc/src/qc_main.c index df6d46e..48ff300 100644 --- a/media/libstagefright/codecs/aacenc/src/qc_main.c +++ b/media/libstagefright/codecs/aacenc/src/qc_main.c @@ -163,7 +163,7 @@ void QCOutDelete(QC_OUT* hQC, VO_MEM_OPERATOR *pMemOP) Word32 i; if(hQC) { - if(hQC->qcChannel[0].quantSpec); + if(hQC->qcChannel[0].quantSpec) mem_free(pMemOP, hQC->qcChannel[0].quantSpec, VO_INDEX_ENC_AAC); if(hQC->qcChannel[0].maxValueInSfb) diff --git a/media/libstagefright/codecs/aacenc/src/quantize.c b/media/libstagefright/codecs/aacenc/src/quantize.c index 54add2f..0d0f550 100644 --- a/media/libstagefright/codecs/aacenc/src/quantize.c +++ b/media/libstagefright/codecs/aacenc/src/quantize.c @@ -110,7 +110,7 @@ static void quantizeLines(const Word16 gain, Word32 m = gain&3; Word32 g = (gain >> 2) + 4; Word32 mdctSpeL; - Word16 *pquat; + const Word16 *pquat; /* gain&3 */ pquat = quantBorders[m]; @@ -333,7 +333,7 @@ Word32 calcSfbDist(const Word32 *spec, Word32 m = gain&3; Word32 g = (gain >> 2) + 4; Word32 g2 = (g << 1) + 1; - Word16 *pquat, *repquat; + const Word16 *pquat, *repquat; /* gain&3 */ pquat = quantBorders[m]; diff --git a/media/libstagefright/codecs/aacenc/src/sf_estim.c b/media/libstagefright/codecs/aacenc/src/sf_estim.c index fe40137..bc320ec 100644 --- a/media/libstagefright/codecs/aacenc/src/sf_estim.c +++ b/media/libstagefright/codecs/aacenc/src/sf_estim.c @@ -400,7 +400,7 @@ static void assimilateSingleScf(PSY_OUT_CHANNEL *psyOutChan, Word16 *minScfCalculated, Flag restartOnSuccess) { - Word32 sfbLast, sfbAct, sfbNext, scfAct, scfMin; + Word16 sfbLast, sfbAct, sfbNext, scfAct, scfMin; Word16 *scfLast, *scfNext; Word32 sfbPeOld, sfbPeNew; Word32 sfbDistNew; diff --git a/media/libstagefright/codecs/aacenc/src/transform.c b/media/libstagefright/codecs/aacenc/src/transform.c index a154a2f..a02336f 100644 --- a/media/libstagefright/codecs/aacenc/src/transform.c +++ b/media/libstagefright/codecs/aacenc/src/transform.c @@ -339,6 +339,12 @@ static void PostMDCT(int *buf0, int num, const int *csptr) *buf1-- = MULHIGH(cosb, tr2) + MULHIGH(sinb, ti2); } } +#else +void Radix4First(int *buf, int num); +void Radix8First(int *buf, int num); +void Radix4FFT(int *buf, int num, int bgn, int *twidTab); +void PreMDCT(int *buf0, int num, const int *csptr); +void PostMDCT(int *buf0, int num, const int *csptr); #endif diff --git a/media/libstagefright/codecs/amrnb/common/include/az_lsp.h b/media/libstagefright/codecs/amrnb/common/include/az_lsp.h index 3e15ba3..7c24ca9 100644 --- a/media/libstagefright/codecs/amrnb/common/include/az_lsp.h +++ b/media/libstagefright/codecs/amrnb/common/include/az_lsp.h @@ -83,7 +83,7 @@ extern "C" ; EXTERNAL VARIABLES REFERENCES ; Declare variables used in this module but defined elsewhere ----------------------------------------------------------------------------*/ - extern Word16 grid[]; + extern const Word16 grid[]; /*---------------------------------------------------------------------------- ; SIMPLE TYPEDEF'S diff --git a/media/libstagefright/codecs/amrnb/common/include/inv_sqrt.h b/media/libstagefright/codecs/amrnb/common/include/inv_sqrt.h index 4fb2b11..91ab3e4 100644 --- a/media/libstagefright/codecs/amrnb/common/include/inv_sqrt.h +++ b/media/libstagefright/codecs/amrnb/common/include/inv_sqrt.h @@ -85,7 +85,7 @@ extern "C" ; EXTERNAL VARIABLES REFERENCES ; Declare variables used in this module but defined elsewhere ----------------------------------------------------------------------------*/ - extern Word16 inv_sqrt_tbl[]; + extern const Word16 inv_sqrt_tbl[]; /*---------------------------------------------------------------------------- ; SIMPLE TYPEDEF'S ----------------------------------------------------------------------------*/ diff --git a/media/libstagefright/codecs/amrnb/common/include/log2_norm.h b/media/libstagefright/codecs/amrnb/common/include/log2_norm.h index b104a69..46b4e4d 100644 --- a/media/libstagefright/codecs/amrnb/common/include/log2_norm.h +++ b/media/libstagefright/codecs/amrnb/common/include/log2_norm.h @@ -85,7 +85,7 @@ extern "C" ; EXTERNAL VARIABLES REFERENCES ; Declare variables used in this module but defined elsewhere ----------------------------------------------------------------------------*/ - extern Word16 log2_tbl[]; + extern const Word16 log2_tbl[]; /*---------------------------------------------------------------------------- ; SIMPLE TYPEDEF'S ----------------------------------------------------------------------------*/ diff --git a/media/libstagefright/codecs/amrnb/common/include/pow2.h b/media/libstagefright/codecs/amrnb/common/include/pow2.h index c96fbdd..9b944eb 100644 --- a/media/libstagefright/codecs/amrnb/common/include/pow2.h +++ b/media/libstagefright/codecs/amrnb/common/include/pow2.h @@ -81,7 +81,7 @@ extern "C" ; EXTERNAL VARIABLES REFERENCES ; Declare variables used in this module but defined elsewhere ----------------------------------------------------------------------------*/ - extern Word16 pow2_tbl[]; + extern const Word16 pow2_tbl[]; /*---------------------------------------------------------------------------- ; SIMPLE TYPEDEF'S ----------------------------------------------------------------------------*/ diff --git a/media/libstagefright/codecs/amrnb/common/include/sqrt_l.h b/media/libstagefright/codecs/amrnb/common/include/sqrt_l.h index 86209bd..a6a2ee5 100644 --- a/media/libstagefright/codecs/amrnb/common/include/sqrt_l.h +++ b/media/libstagefright/codecs/amrnb/common/include/sqrt_l.h @@ -82,7 +82,7 @@ extern "C" ; EXTERNAL VARIABLES REFERENCES ; Declare variables used in this module but defined elsewhere ----------------------------------------------------------------------------*/ - extern Word16 sqrt_l_tbl[]; + extern const Word16 sqrt_l_tbl[]; /*---------------------------------------------------------------------------- ; SIMPLE TYPEDEF'S diff --git a/media/libstagefright/codecs/amrnb/common/src/az_lsp.cpp b/media/libstagefright/codecs/amrnb/common/src/az_lsp.cpp index bd99b30..4135f30 100644 --- a/media/libstagefright/codecs/amrnb/common/src/az_lsp.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/az_lsp.cpp @@ -299,7 +299,7 @@ static Word16 Chebps(Word16 x, t0 += (Word32) * (p_f) << 13; - if ((UWord32)(t0 - 0xfe000000L) < 0x01ffffffL - 0xfe000000L) + if ((UWord32)(t0 - 0xfe000000L) < (UWord32)0x03ffffffL) { cheb = (Word16)(t0 >> 10); } diff --git a/media/libstagefright/codecs/amrnb/common/src/bitno_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/bitno_tab.cpp index fed684d..4ee04a5 100644 --- a/media/libstagefright/codecs/amrnb/common/src/bitno_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/bitno_tab.cpp @@ -152,7 +152,7 @@ extern "C" ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ /* number of parameters per modes (values must be <= MAX_PRM_SIZE!) */ - extern const Word16 prmno[N_MODES] = + const Word16 prmno[N_MODES] = { PRMNO_MR475, PRMNO_MR515, @@ -166,7 +166,7 @@ extern "C" }; /* number of parameters to first subframe per modes */ - extern const Word16 prmnofsf[N_MODES - 1] = + const Word16 prmnofsf[N_MODES - 1] = { PRMNOFSF_MR475, PRMNOFSF_MR515, @@ -179,7 +179,7 @@ extern "C" }; /* parameter sizes (# of bits), one table per mode */ - extern const Word16 bitno_MR475[PRMNO_MR475] = + const Word16 bitno_MR475[PRMNO_MR475] = { 8, 8, 7, /* LSP VQ */ 8, 7, 2, 8, /* first subframe */ @@ -188,7 +188,7 @@ extern "C" 4, 7, 2, /* fourth subframe */ }; - extern const Word16 bitno_MR515[PRMNO_MR515] = + const Word16 bitno_MR515[PRMNO_MR515] = { 8, 8, 7, /* LSP VQ */ 8, 7, 2, 6, /* first subframe */ @@ -197,7 +197,7 @@ extern "C" 4, 7, 2, 6, /* fourth subframe */ }; - extern const Word16 bitno_MR59[PRMNO_MR59] = + const Word16 bitno_MR59[PRMNO_MR59] = { 8, 9, 9, /* LSP VQ */ 8, 9, 2, 6, /* first subframe */ @@ -206,7 +206,7 @@ extern "C" 4, 9, 2, 6, /* fourth subframe */ }; - extern const Word16 bitno_MR67[PRMNO_MR67] = + const Word16 bitno_MR67[PRMNO_MR67] = { 8, 9, 9, /* LSP VQ */ 8, 11, 3, 7, /* first subframe */ @@ -215,7 +215,7 @@ extern "C" 4, 11, 3, 7, /* fourth subframe */ }; - extern const Word16 bitno_MR74[PRMNO_MR74] = + const Word16 bitno_MR74[PRMNO_MR74] = { 8, 9, 9, /* LSP VQ */ 8, 13, 4, 7, /* first subframe */ @@ -224,7 +224,7 @@ extern "C" 5, 13, 4, 7, /* fourth subframe */ }; - extern const Word16 bitno_MR795[PRMNO_MR795] = + const Word16 bitno_MR795[PRMNO_MR795] = { 9, 9, 9, /* LSP VQ */ 8, 13, 4, 4, 5, /* first subframe */ @@ -233,7 +233,7 @@ extern "C" 6, 13, 4, 4, 5, /* fourth subframe */ }; - extern const Word16 bitno_MR102[PRMNO_MR102] = + const Word16 bitno_MR102[PRMNO_MR102] = { 8, 9, 9, /* LSP VQ */ 8, 1, 1, 1, 1, 10, 10, 7, 7, /* first subframe */ @@ -242,7 +242,7 @@ extern "C" 5, 1, 1, 1, 1, 10, 10, 7, 7, /* fourth subframe */ }; - extern const Word16 bitno_MR122[PRMNO_MR122] = + const Word16 bitno_MR122[PRMNO_MR122] = { 7, 8, 9, 8, 6, /* LSP VQ */ 9, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5, /* first subframe */ @@ -251,7 +251,7 @@ extern "C" 6, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5 /* fourth subframe */ }; - extern const Word16 bitno_MRDTX[PRMNO_MRDTX] = + const Word16 bitno_MRDTX[PRMNO_MRDTX] = { 3, 8, 9, 9, @@ -259,7 +259,7 @@ extern "C" }; /* overall table with all parameter sizes for all modes */ - extern const Word16 * const bitno[N_MODES] = + const Word16 * const bitno[N_MODES] = { bitno_MR475, bitno_MR515, diff --git a/media/libstagefright/codecs/amrnb/common/src/bitreorder_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/bitreorder_tab.cpp index 69b20fb..e284bbc 100644 --- a/media/libstagefright/codecs/amrnb/common/src/bitreorder_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/bitreorder_tab.cpp @@ -123,6 +123,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "bitreorder_tab.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -171,7 +172,7 @@ extern "C" ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ /* number of parameters per modes (values must be <= MAX_PRM_SIZE!) */ - extern const Word16 numOfBits[NUM_MODES] = + const Word16 numOfBits[NUM_MODES] = { NUMBIT_MR475, NUMBIT_MR515, @@ -191,7 +192,7 @@ extern "C" NUMBIT_NO_DATA }; - extern const Word16 reorderBits_MR475[NUMBIT_MR475] = + const Word16 reorderBits_MR475[NUMBIT_MR475] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 23, 24, 25, 26, @@ -205,7 +206,7 @@ extern "C" 92, 31, 52, 65, 86 }; - extern const Word16 reorderBits_MR515[NUMBIT_MR515] = + const Word16 reorderBits_MR515[NUMBIT_MR515] = { 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 24, 25, 26, @@ -220,7 +221,7 @@ extern "C" 53, 72, 91 }; - extern const Word16 reorderBits_MR59[NUMBIT_MR59] = + const Word16 reorderBits_MR59[NUMBIT_MR59] = { 0, 1, 4, 5, 3, 6, 7, 2, 13, 15, 8, 9, 11, 12, 14, 10, 16, 28, 74, 29, @@ -236,7 +237,7 @@ extern "C" 38, 59, 84, 105, 37, 58, 83, 104 }; - extern const Word16 reorderBits_MR67[NUMBIT_MR67] = + const Word16 reorderBits_MR67[NUMBIT_MR67] = { 0, 1, 4, 3, 5, 6, 13, 7, 2, 8, 9, 11, 15, 12, 14, 10, 28, 82, 29, 83, @@ -254,7 +255,7 @@ extern "C" 36, 61, 90, 115 }; - extern const Word16 reorderBits_MR74[NUMBIT_MR74] = + const Word16 reorderBits_MR74[NUMBIT_MR74] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 26, 87, 27, @@ -273,7 +274,7 @@ extern "C" 39, 68, 100, 129, 40, 69, 101, 130 }; - extern const Word16 reorderBits_MR795[NUMBIT_MR795] = + const Word16 reorderBits_MR795[NUMBIT_MR795] = { 8, 7, 6, 5, 4, 3, 2, 14, 16, 9, 10, 12, 13, 15, 11, 17, 20, 22, 24, 23, @@ -293,7 +294,7 @@ extern "C" 139, 37, 69, 103, 135, 38, 70, 104, 136 }; - extern const Word16 reorderBits_MR102[NUMBIT_MR102] = + const Word16 reorderBits_MR102[NUMBIT_MR102] = { 7, 6, 5, 4, 3, 2, 1, 0, 16, 15, 14, 13, 12, 11, 10, 9, 8, 26, 27, 28, @@ -318,7 +319,7 @@ extern "C" 63, 46, 55, 56 }; - extern const Word16 reorderBits_MR122[NUMBIT_MR122] = + const Word16 reorderBits_MR122[NUMBIT_MR122] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 23, 15, 16, 17, 18, @@ -348,7 +349,7 @@ extern "C" }; /* overall table with all parameter sizes for all modes */ - extern const Word16 * const reorderBits[NUM_MODES-1] = + const Word16 * const reorderBits[NUM_MODES-1] = { reorderBits_MR475, reorderBits_MR515, @@ -361,7 +362,7 @@ extern "C" }; /* Number of Frames (16-bit segments sent for each mode */ - extern const Word16 numCompressedBytes[16] = + const Word16 numCompressedBytes[16] = { 13, /*4.75*/ 14, /*5.15*/ diff --git a/media/libstagefright/codecs/amrnb/common/src/bytesused.cpp b/media/libstagefright/codecs/amrnb/common/src/bytesused.cpp index 9552206..b61bac4 100644 --- a/media/libstagefright/codecs/amrnb/common/src/bytesused.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/bytesused.cpp @@ -152,7 +152,7 @@ extern "C" ; LOCAL STORE/BUFFER/POINTER DEFINITIONS ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ - extern const short BytesUsed[16] = + const short BytesUsed[16] = { 13, /* 4.75 */ 14, /* 5.15 */ diff --git a/media/libstagefright/codecs/amrnb/common/src/c2_9pf_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/c2_9pf_tab.cpp index 471bee8..20de9d6 100644 --- a/media/libstagefright/codecs/amrnb/common/src/c2_9pf_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/c2_9pf_tab.cpp @@ -86,7 +86,8 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 startPos[2*4*2] = {0, 2, 0, 3, + extern const Word16 startPos[]; + const Word16 startPos[2*4*2] = {0, 2, 0, 3, 0, 2, 0, 3, 1, 3, 2, 4, 1, 4, 1, 4 diff --git a/media/libstagefright/codecs/amrnb/common/src/gains_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/gains_tbl.cpp index a08dd2d..a7cd6fb 100644 --- a/media/libstagefright/codecs/amrnb/common/src/gains_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/gains_tbl.cpp @@ -86,14 +86,16 @@ extern "C" ----------------------------------------------------------------------------*/ - extern const Word16 qua_gain_pitch[NB_QUA_PITCH] = + extern const Word16 qua_gain_pitch[]; + const Word16 qua_gain_pitch[NB_QUA_PITCH] = { 0, 3277, 6556, 8192, 9830, 11469, 12288, 13107, 13926, 14746, 15565, 16384, 17203, 18022, 18842, 19661 }; - extern const Word16 qua_gain_code[(NB_QUA_CODE+1)*3] = + extern const Word16 qua_gain_code[]; + const Word16 qua_gain_code[(NB_QUA_CODE+1)*3] = { /* gain factor (g_fac) and quantized energy error (qua_ener_MR122, qua_ener) * are stored: diff --git a/media/libstagefright/codecs/amrnb/common/src/gray_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/gray_tbl.cpp index 99073d9..c4b2dbc 100644 --- a/media/libstagefright/codecs/amrnb/common/src/gray_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/gray_tbl.cpp @@ -83,8 +83,10 @@ extern "C" ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 gray[8] = {0, 1, 3, 2, 6, 4, 5, 7}; - extern const Word16 dgray[8] = {0, 1, 3, 2, 5, 6, 4, 7}; + extern const Word16 gray[]; + extern const Word16 dgray[]; + const Word16 gray[8] = {0, 1, 3, 2, 6, 4, 5, 7}; + const Word16 dgray[8] = {0, 1, 3, 2, 5, 6, 4, 7}; /*--------------------------------------------------------------------------*/ #ifdef __cplusplus diff --git a/media/libstagefright/codecs/amrnb/common/src/grid_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/grid_tbl.cpp index cd81566..48566cc 100644 --- a/media/libstagefright/codecs/amrnb/common/src/grid_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/grid_tbl.cpp @@ -63,6 +63,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "az_lsp.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -91,7 +92,7 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 grid[grid_points + 1] = + const Word16 grid[grid_points + 1] = { 32760, 32723, 32588, 32364, 32051, 31651, 31164, 30591, 29935, 29196, 28377, 27481, diff --git a/media/libstagefright/codecs/amrnb/common/src/inv_sqrt_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/inv_sqrt_tbl.cpp index bde2c4e..13c3b24 100644 --- a/media/libstagefright/codecs/amrnb/common/src/inv_sqrt_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/inv_sqrt_tbl.cpp @@ -55,6 +55,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "inv_sqrt.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -82,7 +83,7 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 inv_sqrt_tbl[49] = + const Word16 inv_sqrt_tbl[49] = { 32767, 31790, 30894, 30070, 29309, 28602, 27945, 27330, 26755, 26214, diff --git a/media/libstagefright/codecs/amrnb/common/src/log2_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/log2_tbl.cpp index 25d63b2..9b9b099 100644 --- a/media/libstagefright/codecs/amrnb/common/src/log2_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/log2_tbl.cpp @@ -54,6 +54,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "log2_norm.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -82,7 +83,7 @@ extern "C" ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 log2_tbl[33] = + const Word16 log2_tbl[33] = { 0, 1455, 2866, 4236, 5568, 6863, 8124, 9352, 10549, 11716, 12855, 13967, 15054, 16117, 17156, 18172, 19167, 20142, 21097, 22033, diff --git a/media/libstagefright/codecs/amrnb/common/src/lsp_lsf_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/lsp_lsf_tbl.cpp index cee0f32..ddeeba4 100644 --- a/media/libstagefright/codecs/amrnb/common/src/lsp_lsf_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/lsp_lsf_tbl.cpp @@ -77,7 +77,8 @@ extern "C" ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 table[65] = + extern const Word16 table[]; + const Word16 table[65] = { 32767, 32729, 32610, 32413, 32138, 31786, 31357, 30853, 30274, 29622, 28899, 28106, 27246, 26320, 25330, 24279, @@ -94,7 +95,8 @@ extern "C" /* slope used to compute y = acos(x) */ - extern const Word16 slope[64] = + extern const Word16 slope[]; + const Word16 slope[64] = { -26887, -8812, -5323, -3813, -2979, -2444, -2081, -1811, -1608, -1450, -1322, -1219, -1132, -1059, -998, -946, diff --git a/media/libstagefright/codecs/amrnb/common/src/lsp_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/lsp_tab.cpp index deded93..0a32dd7 100644 --- a/media/libstagefright/codecs/amrnb/common/src/lsp_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/lsp_tab.cpp @@ -117,6 +117,7 @@ terms listed above has been obtained from the copyright holder. ----------------------------------------------------------------------------*/ #include "typedef.h" #include "cnst.h" +#include "lsp_tab.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -146,7 +147,7 @@ extern "C" ; LOCAL STORE/BUFFER/POINTER DEFINITIONS ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ - extern const Word16 lsp_init_data[M] = {30000, 26000, 21000, 15000, 8000, + const Word16 lsp_init_data[M] = {30000, 26000, 21000, 15000, 8000, 0, -8000, -15000, -21000, -26000 }; diff --git a/media/libstagefright/codecs/amrnb/common/src/overflow_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/overflow_tbl.cpp index e5d42d6..c4a016d 100644 --- a/media/libstagefright/codecs/amrnb/common/src/overflow_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/overflow_tbl.cpp @@ -81,7 +81,7 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word32 overflow_tbl [32] = {0x7fffffffL, 0x3fffffffL, + const Word32 overflow_tbl [32] = {0x7fffffffL, 0x3fffffffL, 0x1fffffffL, 0x0fffffffL, 0x07ffffffL, 0x03ffffffL, 0x01ffffffL, 0x00ffffffL, diff --git a/media/libstagefright/codecs/amrnb/common/src/ph_disp_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/ph_disp_tab.cpp index 99725df..d568b78 100644 --- a/media/libstagefright/codecs/amrnb/common/src/ph_disp_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/ph_disp_tab.cpp @@ -81,14 +81,16 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 ph_imp_low_MR795[40] = + extern const Word16 ph_imp_low_MR795[]; + const Word16 ph_imp_low_MR795[40] = { 26777, 801, 2505, -683, -1382, 582, 604, -1274, 3511, -5894, 4534, -499, -1940, 3011, -5058, 5614, -1990, -1061, -1459, 4442, -700, -5335, 4609, 452, -589, -3352, 2953, 1267, -1212, -2590, 1731, 3670, -4475, -975, 4391, -2537, 949, -1363, -979, 5734 }; - extern const Word16 ph_imp_mid_MR795[40] = + extern const Word16 ph_imp_mid_MR795[]; + const Word16 ph_imp_mid_MR795[40] = { 30274, 3831, -4036, 2972, -1048, -1002, 2477, -3043, 2815, -2231, 1753, -1611, 1714, -1775, 1543, -1008, 429, -169, 472, -1264, @@ -96,14 +98,16 @@ extern "C" -2063, 2644, -3060, 2897, -1978, 557, 780, -1369, 842, 655 }; - extern const Word16 ph_imp_low[40] = + extern const Word16 ph_imp_low[]; + const Word16 ph_imp_low[40] = { 14690, 11518, 1268, -2761, -5671, 7514, -35, -2807, -3040, 4823, 2952, -8424, 3785, 1455, 2179, -8637, 8051, -2103, -1454, 777, 1108, -2385, 2254, -363, -674, -2103, 6046, -5681, 1072, 3123, -5058, 5312, -2329, -3728, 6924, -3889, 675, -1775, 29, 10145 }; - extern const Word16 ph_imp_mid[40] = + extern const Word16 ph_imp_mid[]; + const Word16 ph_imp_mid[40] = { 30274, 3831, -4036, 2972, -1048, -1002, 2477, -3043, 2815, -2231, 1753, -1611, 1714, -1775, 1543, -1008, 429, -169, 472, -1264, diff --git a/media/libstagefright/codecs/amrnb/common/src/pow2_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/pow2_tbl.cpp index e0183a6..902ea0f 100644 --- a/media/libstagefright/codecs/amrnb/common/src/pow2_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/pow2_tbl.cpp @@ -53,6 +53,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "pow2.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -81,7 +82,7 @@ extern "C" ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 pow2_tbl[33] = + const Word16 pow2_tbl[33] = { 16384, 16743, 17109, 17484, 17867, 18258, 18658, 19066, 19484, 19911, 20347, 20792, 21247, 21713, 22188, 22674, 23170, 23678, 24196, 24726, diff --git a/media/libstagefright/codecs/amrnb/common/src/q_plsf_5_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/q_plsf_5_tbl.cpp index ceb1e1e..caa81cb 100644 --- a/media/libstagefright/codecs/amrnb/common/src/q_plsf_5_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/q_plsf_5_tbl.cpp @@ -56,6 +56,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "q_plsf_5_tbl.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -94,7 +95,7 @@ extern "C" ----------------------------------------------------------------------------*/ /* LSF means ->normalize frequency domain */ - extern const Word16 mean_lsf_5[10] = + const Word16 mean_lsf_5[10] = { 1384, 2077, @@ -108,7 +109,7 @@ extern "C" 13701 }; - extern const Word16 dico1_lsf_5[DICO1_5_SIZE * 4] = + const Word16 dico1_lsf_5[DICO1_5_SIZE * 4] = { -451, -1065, -529, -1305, -450, -756, -497, -863, @@ -240,7 +241,7 @@ extern "C" 1469, 2181, 1443, 2016 }; - extern const Word16 dico2_lsf_5[DICO2_5_SIZE * 4] = + const Word16 dico2_lsf_5[DICO2_5_SIZE * 4] = { -1631, -1600, -1796, -2290, -1027, -1770, -1100, -2025, @@ -500,7 +501,7 @@ extern "C" 2374, 2787, 1821, 2788 }; - extern const Word16 dico3_lsf_5[DICO3_5_SIZE * 4] = + const Word16 dico3_lsf_5[DICO3_5_SIZE * 4] = { -1812, -2275, -1879, -2537, -1640, -1848, -1695, -2004, @@ -760,7 +761,7 @@ extern "C" 2180, 1975, 2326, 2020 }; - extern const Word16 dico4_lsf_5[DICO4_5_SIZE * 4] = + const Word16 dico4_lsf_5[DICO4_5_SIZE * 4] = { -1857, -1681, -1857, -1755, -2056, -1150, -2134, -1654, @@ -1020,7 +1021,7 @@ extern "C" 1716, 1376, 1948, 1465 }; - extern const Word16 dico5_lsf_5[DICO5_5_SIZE * 4] = + const Word16 dico5_lsf_5[DICO5_5_SIZE * 4] = { -1002, -929, -1096, -1203, -641, -931, -604, -961, diff --git a/media/libstagefright/codecs/amrnb/common/src/qua_gain_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/qua_gain_tbl.cpp index 52f77e9..2d913b8 100644 --- a/media/libstagefright/codecs/amrnb/common/src/qua_gain_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/qua_gain_tbl.cpp @@ -54,6 +54,7 @@ terms listed above has been obtained from the copyright holder. ----------------------------------------------------------------------------*/ #include "typedef.h" #include "qua_gain.h" +#include "qua_gain_tbl.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -96,7 +97,7 @@ extern "C" /* table used in 'high' rates: MR67 MR74 */ - extern const Word16 table_gain_highrates[VQ_SIZE_HIGHRATES*4] = + const Word16 table_gain_highrates[VQ_SIZE_HIGHRATES*4] = { /* @@ -240,7 +241,7 @@ extern "C" /* table used in 'low' rates: MR475, MR515, MR59 */ - extern const Word16 table_gain_lowrates[VQ_SIZE_LOWRATES*4] = + const Word16 table_gain_lowrates[VQ_SIZE_LOWRATES*4] = { /*g_pit, g_fac, qua_ener_MR122, qua_ener */ 10813, 28753, 2879, 17333, diff --git a/media/libstagefright/codecs/amrnb/common/src/sqrt_l_tbl.cpp b/media/libstagefright/codecs/amrnb/common/src/sqrt_l_tbl.cpp index 5e9898c..5a84b63 100644 --- a/media/libstagefright/codecs/amrnb/common/src/sqrt_l_tbl.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/sqrt_l_tbl.cpp @@ -58,6 +58,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "sqrt_l.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -85,7 +86,7 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 sqrt_l_tbl[50] = + const Word16 sqrt_l_tbl[50] = { 16384, 16888, 17378, 17854, 18318, 18770, 19212, 19644, 20066, 20480, 20886, 21283, 21674, 22058, 22435, 22806, 23170, 23530, 23884, 24232, diff --git a/media/libstagefright/codecs/amrnb/common/src/window_tab.cpp b/media/libstagefright/codecs/amrnb/common/src/window_tab.cpp index fa5faa6..d8fc8cc 100644 --- a/media/libstagefright/codecs/amrnb/common/src/window_tab.cpp +++ b/media/libstagefright/codecs/amrnb/common/src/window_tab.cpp @@ -117,6 +117,7 @@ terms listed above has been obtained from the copyright holder. ----------------------------------------------------------------------------*/ #include "typedef.h" #include "cnst.h" +#include "window_tab.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -154,7 +155,7 @@ extern "C" /* window for non-EFR modesm; uses 40 samples lookahead */ - extern const Word16 window_200_40[L_WINDOW] = + const Word16 window_200_40[L_WINDOW] = { 2621, 2623, 2629, 2638, 2651, 2668, 2689, 2713, 2741, 2772, 2808, 2847, 2890, 2936, 2986, 3040, 3097, 3158, 3223, 3291, @@ -185,7 +186,7 @@ extern "C" /* window for EFR, first two subframes, no lookahead */ - extern const Word16 window_160_80[L_WINDOW] = + const Word16 window_160_80[L_WINDOW] = { 2621, 2624, 2633, 2648, 2668, 2695, 2727, 2765, 2809, 2859, 2915, 2976, 3043, 3116, 3194, 3279, 3368, 3464, 3565, 3671, @@ -215,7 +216,7 @@ extern "C" /* window for EFR, last two subframes, no lookahead */ - extern const Word16 window_232_8[L_WINDOW] = + const Word16 window_232_8[L_WINDOW] = { 2621, 2623, 2627, 2634, 2644, 2656, 2671, 2689, 2710, 2734, 2760, 2789, 2821, 2855, 2893, 2933, 2975, 3021, 3069, 3120, diff --git a/media/libstagefright/codecs/amrnb/dec/src/dec_input_format_tab.cpp b/media/libstagefright/codecs/amrnb/dec/src/dec_input_format_tab.cpp index a59f5fa..fffbbfd 100644 --- a/media/libstagefright/codecs/amrnb/dec/src/dec_input_format_tab.cpp +++ b/media/libstagefright/codecs/amrnb/dec/src/dec_input_format_tab.cpp @@ -121,6 +121,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "amrdecode.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -152,7 +153,7 @@ extern "C" ----------------------------------------------------------------------------*/ /* Table containing the number of core AMR data bytes for */ /* each codec mode for WMF input format(number excludes frame type byte) */ - extern const Word16 WmfDecBytesPerFrame[16] = + const Word16 WmfDecBytesPerFrame[16] = { 12, /* 4.75 */ 13, /* 5.15 */ @@ -174,7 +175,7 @@ extern "C" /* Table containing the number of core AMR data bytes for */ /* each codec mode for IF2 input format. */ - extern const Word16 If2DecBytesPerFrame[16] = + const Word16 If2DecBytesPerFrame[16] = { 13, /* 4.75 */ 14, /* 5.15 */ diff --git a/media/libstagefright/codecs/amrnb/dec/src/qgain475_tab.cpp b/media/libstagefright/codecs/amrnb/dec/src/qgain475_tab.cpp index fbcd412..1a08efa 100644 --- a/media/libstagefright/codecs/amrnb/dec/src/qgain475_tab.cpp +++ b/media/libstagefright/codecs/amrnb/dec/src/qgain475_tab.cpp @@ -92,7 +92,7 @@ extern "C" * g_fac(2) (Q12) // frame 1 and 3 * */ - extern const Word16 table_gain_MR475[MR475_VQ_SIZE*4] = + const Word16 table_gain_MR475[MR475_VQ_SIZE*4] = { /*g_pit(0), g_fac(0), g_pit(1), g_fac(1) */ 812, 128, 542, 140, diff --git a/media/libstagefright/codecs/amrnb/enc/src/corrwght_tab.cpp b/media/libstagefright/codecs/amrnb/enc/src/corrwght_tab.cpp index 769e7ba..b3ed02d 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/corrwght_tab.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/corrwght_tab.cpp @@ -57,6 +57,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "p_ol_wgh.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -84,7 +85,7 @@ extern "C" ; LOCAL VARIABLE DEFINITIONS ; [Variable declaration - defined here and used outside this module] ----------------------------------------------------------------------------*/ - extern const Word16 corrweight[251] = + const Word16 corrweight[251] = { 20473, 20506, 20539, 20572, 20605, 20644, 20677, 20716, 20749, 20788, 20821, 20860, 20893, 20932, diff --git a/media/libstagefright/codecs/amrnb/enc/src/enc_output_format_tab.cpp b/media/libstagefright/codecs/amrnb/enc/src/enc_output_format_tab.cpp index 147989f..4551fd7 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/enc_output_format_tab.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/enc_output_format_tab.cpp @@ -117,6 +117,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "amrencode.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -150,7 +151,7 @@ extern "C" /* for WMF output format. */ /* Each entry is the sum of the 3GPP frame type byte and the */ /* number of packed core AMR data bytes */ - extern const Word16 WmfEncBytesPerFrame[16] = + const Word16 WmfEncBytesPerFrame[16] = { 13, /* 4.75 */ 14, /* 5.15 */ @@ -173,7 +174,7 @@ extern "C" /* Number of data bytes in an encoder frame for each codec mode */ /* for IF2 output format */ - extern const Word16 If2EncBytesPerFrame[16] = + const Word16 If2EncBytesPerFrame[16] = { 13, /* 4.75 */ 14, /* 5.15 */ diff --git a/media/libstagefright/codecs/amrnb/enc/src/inter_36_tab.cpp b/media/libstagefright/codecs/amrnb/enc/src/inter_36_tab.cpp index 27f33e9..c8d7b13 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/inter_36_tab.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/inter_36_tab.cpp @@ -123,6 +123,7 @@ terms listed above has been obtained from the copyright holder. ----------------------------------------------------------------------------*/ #include "typedef.h" #include "cnst.h" +#include "inter_36_tab.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -162,7 +163,7 @@ extern "C" inter_3[k] = inter_6[2*k], 0 <= k <= 3*L_INTER_SRCH */ - extern const Word16 inter_6[FIR_SIZE] = + const Word16 inter_6[FIR_SIZE] = { 29519, 28316, 24906, 19838, 13896, 7945, 2755, diff --git a/media/libstagefright/codecs/amrnb/enc/src/lag_wind_tab.cpp b/media/libstagefright/codecs/amrnb/enc/src/lag_wind_tab.cpp index 53889bb..b0f5b3a 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/lag_wind_tab.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/lag_wind_tab.cpp @@ -138,6 +138,7 @@ terms listed above has been obtained from the copyright holder. ; INCLUDES ----------------------------------------------------------------------------*/ #include "typedef.h" +#include "lag_wind_tab.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -167,7 +168,7 @@ extern "C" ; LOCAL STORE/BUFFER/POINTER DEFINITIONS ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ - extern const Word16 lag_h[10] = + const Word16 lag_h[10] = { 32728, 32619, @@ -181,7 +182,7 @@ extern "C" 29321 }; - extern const Word16 lag_l[10] = + const Word16 lag_l[10] = { 11904, 17280, diff --git a/media/libstagefright/codecs/amrnb/enc/src/set_sign.cpp b/media/libstagefright/codecs/amrnb/enc/src/set_sign.cpp index dedf91a..d626de3 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/set_sign.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/set_sign.cpp @@ -552,10 +552,10 @@ void set_sign12k2( else { *(p_sign--) = -32767; /* sign = -1 */ - cor = - (cor); + cor = negate(cor); /* modify dn[] according to the fixed sign */ - dn[i] = - val; + dn[i] = negate(val); } *(p_en--) = cor; diff --git a/media/libstagefright/codecs/amrnb/enc/src/ton_stab.cpp b/media/libstagefright/codecs/amrnb/enc/src/ton_stab.cpp index 3c4494d..455a510 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/ton_stab.cpp +++ b/media/libstagefright/codecs/amrnb/enc/src/ton_stab.cpp @@ -791,7 +791,8 @@ void update_gp_clipping(tonStabState *st, /* i/o : State struct */ ) { OSCL_UNUSED_ARG(pOverflow); - for (int i = 0; i < N_FRAME - 1; i++) + int i; + for (i = 0; i < N_FRAME - 1; i++) { st->gp[i] = st->gp[i+1]; } diff --git a/media/libstagefright/codecs/amrwb/include/pvamrwbdecoder_api.h b/media/libstagefright/codecs/amrwb/include/pvamrwbdecoder_api.h index 457c21f..eca5ae0 100644 --- a/media/libstagefright/codecs/amrwb/include/pvamrwbdecoder_api.h +++ b/media/libstagefright/codecs/amrwb/include/pvamrwbdecoder_api.h @@ -106,7 +106,7 @@ extern "C" #define NUM_OF_MODES 10 - const int16 AMR_WB_COMPRESSED[NUM_OF_MODES] = + static const int16 AMR_WB_COMPRESSED[NUM_OF_MODES] = { NBBITS_7k, NBBITS_9k, diff --git a/media/libstagefright/codecs/amrwb/src/get_amr_wb_bits.cpp b/media/libstagefright/codecs/amrwb/src/get_amr_wb_bits.cpp index d7287f3..b325e8f 100644 --- a/media/libstagefright/codecs/amrwb/src/get_amr_wb_bits.cpp +++ b/media/libstagefright/codecs/amrwb/src/get_amr_wb_bits.cpp @@ -119,8 +119,9 @@ int16 Serial_parm( /* Return the parameter */ ) { int16 value = 0; + int16 i; - for (int16 i = no_of_bits >> 1; i != 0; i--) + for (i = no_of_bits >> 1; i != 0; i--) { value <<= 2; diff --git a/media/libstagefright/codecs/amrwb/src/homing_amr_wb_dec.cpp b/media/libstagefright/codecs/amrwb/src/homing_amr_wb_dec.cpp index 59c6c0a..f032a08 100644 --- a/media/libstagefright/codecs/amrwb/src/homing_amr_wb_dec.cpp +++ b/media/libstagefright/codecs/amrwb/src/homing_amr_wb_dec.cpp @@ -134,7 +134,7 @@ extern "C" ; LOCAL STORE/BUFFER/POINTER DEFINITIONS ; Variable declaration - defined here and used outside this module ----------------------------------------------------------------------------*/ -const int16 prmnofsf[NUM_OF_SPMODES] = +static const int16 prmnofsf[NUM_OF_SPMODES] = { 63, 81, 100, 108, 116, 128, @@ -142,21 +142,21 @@ const int16 prmnofsf[NUM_OF_SPMODES] = }; -const int16 dfh_M7k[PRMN_7k] = +static const int16 dfh_M7k[PRMN_7k] = { 3168, 29954, 29213, 16121, 64, 13440, 30624, 16430, 19008 }; -const int16 dfh_M9k[PRMN_9k] = +static const int16 dfh_M9k[PRMN_9k] = { 3168, 31665, 9943, 9123, 15599, 4358, 20248, 2048, 17040, 27787, 16816, 13888 }; -const int16 dfh_M12k[PRMN_12k] = +static const int16 dfh_M12k[PRMN_12k] = { 3168, 31665, 9943, 9128, 3647, 8129, 30930, 27926, @@ -165,7 +165,7 @@ const int16 dfh_M12k[PRMN_12k] = 13948 }; -const int16 dfh_M14k[PRMN_14k] = +static const int16 dfh_M14k[PRMN_14k] = { 3168, 31665, 9943, 9131, 24815, 655, 26616, 26764, @@ -174,7 +174,7 @@ const int16 dfh_M14k[PRMN_14k] = 221, 20321, 17823 }; -const int16 dfh_M16k[PRMN_16k] = +static const int16 dfh_M16k[PRMN_16k] = { 3168, 31665, 9943, 9131, 24815, 700, 3824, 7271, @@ -184,7 +184,7 @@ const int16 dfh_M16k[PRMN_16k] = 6759, 24576 }; -const int16 dfh_M18k[PRMN_18k] = +static const int16 dfh_M18k[PRMN_18k] = { 3168, 31665, 9943, 9135, 14787, 14423, 30477, 24927, @@ -195,7 +195,7 @@ const int16 dfh_M18k[PRMN_18k] = 0 }; -const int16 dfh_M20k[PRMN_20k] = +static const int16 dfh_M20k[PRMN_20k] = { 3168, 31665, 9943, 9129, 8637, 31807, 24646, 736, @@ -206,7 +206,7 @@ const int16 dfh_M20k[PRMN_20k] = 30249, 29123, 0 }; -const int16 dfh_M23k[PRMN_23k] = +static const int16 dfh_M23k[PRMN_23k] = { 3168, 31665, 9943, 9132, 16748, 3202, 28179, 16317, @@ -218,7 +218,7 @@ const int16 dfh_M23k[PRMN_23k] = 23392, 26053, 31216 }; -const int16 dfh_M24k[PRMN_24k] = +static const int16 dfh_M24k[PRMN_24k] = { 3168, 31665, 9943, 9134, 24776, 5857, 18475, 28535, diff --git a/media/libstagefright/codecs/amrwb/src/isp_isf.cpp b/media/libstagefright/codecs/amrwb/src/isp_isf.cpp index 41db7e3..0552733 100644 --- a/media/libstagefright/codecs/amrwb/src/isp_isf.cpp +++ b/media/libstagefright/codecs/amrwb/src/isp_isf.cpp @@ -108,7 +108,7 @@ terms listed above has been obtained from the copyright holder. /* table of cos(x) in Q15 */ -const int16 table[129] = +static const int16 table[129] = { 32767, 32758, 32729, 32679, 32610, 32522, 32413, 32286, 32138, diff --git a/media/libstagefright/codecs/amrwb/src/oversamp_12k8_to_16k.cpp b/media/libstagefright/codecs/amrwb/src/oversamp_12k8_to_16k.cpp index 143c26e..806851e 100644 --- a/media/libstagefright/codecs/amrwb/src/oversamp_12k8_to_16k.cpp +++ b/media/libstagefright/codecs/amrwb/src/oversamp_12k8_to_16k.cpp @@ -240,11 +240,11 @@ void AmrWbUp_samp( { int32 i; - int16 frac; + int16 frac, j; int16 * pt_sig_u = sig_u; frac = 1; - for (int16 j = 0; j < L_frame; j++) + for (j = 0; j < L_frame; j++) { i = ((int32)j * INV_FAC5) >> 13; /* integer part = pos * 1/5 */ @@ -337,6 +337,6 @@ int16 AmrWbInterpol( /* return result of interpolation */ L_sum = shl_int32(L_sum, 2); /* saturation can occur here */ - return ((int16(L_sum >> 16))); + return ((int16)(L_sum >> 16)); } diff --git a/media/libstagefright/codecs/amrwb/src/phase_dispersion.cpp b/media/libstagefright/codecs/amrwb/src/phase_dispersion.cpp index f90a534..7b08a40 100644 --- a/media/libstagefright/codecs/amrwb/src/phase_dispersion.cpp +++ b/media/libstagefright/codecs/amrwb/src/phase_dispersion.cpp @@ -109,7 +109,7 @@ terms listed above has been obtained from the copyright holder. /* impulse response with phase dispersion */ /* 2.0 - 6.4 kHz phase dispersion */ -const int16 ph_imp_low[L_SUBFR] = +static const int16 ph_imp_low[L_SUBFR] = { 20182, 9693, 3270, -3437, 2864, -5240, 1589, -1357, 600, 3893, -1497, -698, 1203, -5249, 1199, 5371, @@ -122,7 +122,7 @@ const int16 ph_imp_low[L_SUBFR] = }; /* 3.2 - 6.4 kHz phase dispersion */ -const int16 ph_imp_mid[L_SUBFR] = +static const int16 ph_imp_mid[L_SUBFR] = { 24098, 10460, -5263, -763, 2048, -927, 1753, -3323, 2212, 652, -2146, 2487, -3539, 4109, -2107, -374, diff --git a/media/libstagefright/codecs/amrwbenc/inc/isp_isf.tab b/media/libstagefright/codecs/amrwbenc/inc/isp_isf.tab index 97c3b68..865eea0 100644 --- a/media/libstagefright/codecs/amrwbenc/inc/isp_isf.tab +++ b/media/libstagefright/codecs/amrwbenc/inc/isp_isf.tab @@ -21,7 +21,7 @@ /* table of cos(x) in Q15 */ -const static Word16 table[129] = { +static const Word16 table[129] = { 32767, 32758, 32729, 32679, 32610, 32522, 32413, 32286, 32138, 31972, 31786, 31581, 31357, 31114, 30853, 30572, 30274, @@ -42,7 +42,7 @@ const static Word16 table[129] = { /* slope in Q11 used to compute y = acos(x) */ -const static Word16 slope[128] = { +static const Word16 slope[128] = { -26214, -9039, -5243, -3799, -2979, -2405, -2064, -1771, -1579, -1409, -1279, -1170, -1079, -1004, -933, -880, -827, -783, -743, -708, -676, -647, -621, -599, diff --git a/media/libstagefright/codecs/amrwbenc/src/voAMRWBEnc.c b/media/libstagefright/codecs/amrwbenc/src/voAMRWBEnc.c index 0f4d689..ea9da52 100644 --- a/media/libstagefright/codecs/amrwbenc/src/voAMRWBEnc.c +++ b/media/libstagefright/codecs/amrwbenc/src/voAMRWBEnc.c @@ -1702,7 +1702,7 @@ VO_U32 VO_API voAMRWB_SetInputData( gData = (Coder_State *)hCodec; stream = gData->stream; - if(NULL == pInput || NULL == pInput->Buffer || 0 > pInput->Length) + if(NULL == pInput || NULL == pInput->Buffer) { return VO_ERR_INVALID_ARG; } diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp index 74fe478..b3c350f 100644 --- a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp +++ b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp @@ -1041,7 +1041,7 @@ PV_STATUS DecodeShortHeader(VideoDecData *video, Vop *currVop) /* Marker Bit */ if (!BitstreamRead1Bits(stream)) { - mp4dec_log("DecodeShortHeader(): Market bit wrong.\n"); + mp4dec_log("DecodeShortHeader(): Marker bit wrong.\n"); status = PV_FAIL; goto return_point; } diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp index e892f92..059d6b9 100644 --- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp +++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp @@ -19,12 +19,9 @@ #include "../include/SoftwareRenderer.h" -#include <binder/MemoryHeapBase.h> -#include <binder/MemoryHeapPmem.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/MetaData.h> -#include <surfaceflinger/Surface.h> -#include <ui/android_native_buffer.h> +#include <system/window.h> #include <ui/GraphicBufferMapper.h> #include <gui/ISurfaceTexture.h> diff --git a/media/libstagefright/foundation/AMessage.cpp b/media/libstagefright/foundation/AMessage.cpp index 0a6776e..9a00186 100644 --- a/media/libstagefright/foundation/AMessage.cpp +++ b/media/libstagefright/foundation/AMessage.cpp @@ -19,6 +19,7 @@ #include <ctype.h> #include "AAtomizer.h" +#include "ABuffer.h" #include "ADebug.h" #include "ALooperRoster.h" #include "AString.h" @@ -157,14 +158,23 @@ void AMessage::setString( item->u.stringValue = new AString(s, len < 0 ? strlen(s) : len); } -void AMessage::setObject(const char *name, const sp<RefBase> &obj) { +void AMessage::setObjectInternal( + const char *name, const sp<RefBase> &obj, Type type) { Item *item = allocateItem(name); - item->mType = kTypeObject; + item->mType = type; if (obj != NULL) { obj->incStrong(this); } item->u.refValue = obj.get(); } +void AMessage::setObject(const char *name, const sp<RefBase> &obj) { + setObjectInternal(name, obj, kTypeObject); +} + +void AMessage::setBuffer(const char *name, const sp<ABuffer> &buffer) { + setObjectInternal(name, sp<RefBase>(buffer), kTypeBuffer); +} + void AMessage::setMessage(const char *name, const sp<AMessage> &obj) { Item *item = allocateItem(name); item->mType = kTypeMessage; @@ -203,6 +213,15 @@ bool AMessage::findObject(const char *name, sp<RefBase> *obj) const { return false; } +bool AMessage::findBuffer(const char *name, sp<ABuffer> *buf) const { + const Item *item = findItem(name, kTypeBuffer); + if (item) { + *buf = (ABuffer *)(item->u.refValue); + return true; + } + return false; +} + bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const { const Item *item = findItem(name, kTypeMessage); if (item) { @@ -542,4 +561,20 @@ void AMessage::writeToParcel(Parcel *parcel) const { } } +size_t AMessage::countEntries() const { + return mNumItems; +} + +const char *AMessage::getEntryNameAt(size_t index, Type *type) const { + if (index >= mNumItems) { + *type = kTypeInt32; + + return NULL; + } + + *type = mItems[index].mType; + + return mItems[index].mName; +} + } // namespace android diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h index 8f2ea95..7ab0042 100644 --- a/media/libstagefright/include/SoftwareRenderer.h +++ b/media/libstagefright/include/SoftwareRenderer.h @@ -20,7 +20,7 @@ #include <media/stagefright/ColorConverter.h> #include <utils/RefBase.h> -#include <ui/android_native_buffer.h> +#include <system/window.h> namespace android { diff --git a/media/libstagefright/rtsp/AAMRAssembler.cpp b/media/libstagefright/rtsp/AAMRAssembler.cpp index 9d72b1f..fb8abc5 100644 --- a/media/libstagefright/rtsp/AAMRAssembler.cpp +++ b/media/libstagefright/rtsp/AAMRAssembler.cpp @@ -211,7 +211,7 @@ ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket( } sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", accessUnit); + msg->setBuffer("access-unit", accessUnit); msg->post(); queue->erase(queue->begin()); diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp index ed8b1df..7ea132e 100644 --- a/media/libstagefright/rtsp/AAVCAssembler.cpp +++ b/media/libstagefright/rtsp/AAVCAssembler.cpp @@ -345,7 +345,7 @@ void AAVCAssembler::submitAccessUnit() { mAccessUnitDamaged = false; sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", accessUnit); + msg->setBuffer("access-unit", accessUnit); msg->post(); } diff --git a/media/libstagefright/rtsp/AH263Assembler.cpp b/media/libstagefright/rtsp/AH263Assembler.cpp index 498295c..ded70fa 100644 --- a/media/libstagefright/rtsp/AH263Assembler.cpp +++ b/media/libstagefright/rtsp/AH263Assembler.cpp @@ -166,7 +166,7 @@ void AH263Assembler::submitAccessUnit() { mAccessUnitDamaged = false; sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", accessUnit); + msg->setBuffer("access-unit", accessUnit); msg->post(); } diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp index b0c7007..24c2f30 100644 --- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp +++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp @@ -571,7 +571,7 @@ void AMPEG4AudioAssembler::submitAccessUnit() { mAccessUnitDamaged = false; sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", accessUnit); + msg->setBuffer("access-unit", accessUnit); msg->post(); } diff --git a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp index 2f2e2c2..687d72b 100644 --- a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp +++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp @@ -368,7 +368,7 @@ void AMPEG4ElementaryAssembler::submitAccessUnit() { mAccessUnitDamaged = false; sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", accessUnit); + msg->setBuffer("access-unit", accessUnit); msg->post(); } diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp index 8c9dd8d..44988a3 100644 --- a/media/libstagefright/rtsp/ARTPConnection.cpp +++ b/media/libstagefright/rtsp/ARTPConnection.cpp @@ -639,7 +639,7 @@ sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) { void ARTPConnection::injectPacket(int index, const sp<ABuffer> &buffer) { sp<AMessage> msg = new AMessage(kWhatInjectPacket, id()); msg->setInt32("index", index); - msg->setObject("buffer", buffer); + msg->setBuffer("buffer", buffer); msg->post(); } @@ -647,10 +647,8 @@ void ARTPConnection::onInjectPacket(const sp<AMessage> &msg) { int32_t index; CHECK(msg->findInt32("index", &index)); - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); List<StreamInfo>::iterator it = mStreams.begin(); while (it != mStreams.end() diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp index 7a05b88..ba4e33c 100644 --- a/media/libstagefright/rtsp/ARTPSession.cpp +++ b/media/libstagefright/rtsp/ARTPSession.cpp @@ -145,10 +145,8 @@ void ARTPSession::onMessageReceived(const sp<AMessage> &msg) { break; } - sp<RefBase> obj; - CHECK(msg->findObject("access-unit", &obj)); - - sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("access-unit", &accessUnit)); uint64_t ntpTime; CHECK(accessUnit->meta()->findInt64( diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index 80a010e..539a888 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -612,7 +612,7 @@ bool ARTSPConnection::receiveRTSPReponse() { if (mObserveBinaryMessage != NULL) { sp<AMessage> notify = mObserveBinaryMessage->dup(); - notify->setObject("buffer", buffer); + notify->setBuffer("buffer", buffer); notify->post(); } else { ALOGW("received binary data, but no one cares."); diff --git a/media/libstagefright/rtsp/ARawAudioAssembler.cpp b/media/libstagefright/rtsp/ARawAudioAssembler.cpp index 98bee82..0da5dd2 100644 --- a/media/libstagefright/rtsp/ARawAudioAssembler.cpp +++ b/media/libstagefright/rtsp/ARawAudioAssembler.cpp @@ -94,7 +94,7 @@ ARTPAssembler::AssemblyStatus ARawAudioAssembler::addPacket( } sp<AMessage> msg = mNotifyMsg->dup(); - msg->setObject("access-unit", buffer); + msg->setBuffer("access-unit", buffer); msg->post(); queue->erase(queue->begin()); diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index 9a7dd70..deee30f 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -857,10 +857,8 @@ struct MyHandler : public AHandler { return; } - sp<RefBase> obj; - CHECK(msg->findObject("access-unit", &obj)); - - sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("access-unit", &accessUnit)); uint32_t seqNum = (uint32_t)accessUnit->int32Data(); @@ -1005,9 +1003,8 @@ struct MyHandler : public AHandler { case 'biny': { - sp<RefBase> obj; - CHECK(msg->findObject("buffer", &obj)); - sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); int32_t index; CHECK(buffer->meta()->findInt32("index", &index)); @@ -1488,7 +1485,7 @@ private: sp<AMessage> msg = mNotify->dup(); msg->setInt32("what", kWhatAccessUnit); msg->setSize("trackIndex", trackIndex); - msg->setObject("accessUnit", accessUnit); + msg->setBuffer("accessUnit", accessUnit); msg->post(); } diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp index d7cec04..3dcd9fc 100644 --- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp +++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp @@ -26,11 +26,11 @@ #include <media/stagefright/SurfaceMediaSource.h> #include <media/mediarecorder.h> -#include <gui/SurfaceTextureClient.h> #include <ui/GraphicBuffer.h> -#include <surfaceflinger/ISurfaceComposer.h> -#include <surfaceflinger/Surface.h> -#include <surfaceflinger/SurfaceComposerClient.h> +#include <gui/SurfaceTextureClient.h> +#include <gui/ISurfaceComposer.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> #include <binder/ProcessState.h> #include <ui/FramebufferNativeWindow.h> diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk index 8b23dee..d2d5f7b 100644 --- a/media/libstagefright/timedtext/Android.mk +++ b/media/libstagefright/timedtext/Android.mk @@ -4,7 +4,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ TextDescriptions.cpp \ TimedTextDriver.cpp \ - TimedTextInBandSource.cpp \ + TimedText3GPPSource.cpp \ TimedTextSource.cpp \ TimedTextSRTSource.cpp \ TimedTextPlayer.cpp @@ -12,8 +12,8 @@ LOCAL_SRC_FILES:= \ LOCAL_CFLAGS += -Wno-multichar LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ - $(TOP)/frameworks/base/media/libstagefright \ - $(TOP)/frameworks/base/include/media/stagefright/openmax + $(TOP)/frameworks/base/include/media/stagefright/timedtext \ + $(TOP)/frameworks/base/media/libstagefright LOCAL_MODULE:= libstagefright_timedtext diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp index afb73fb..4a3bfd3 100644 --- a/media/libstagefright/timedtext/TimedTextInBandSource.cpp +++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp @@ -15,7 +15,7 @@ */ //#define LOG_NDEBUG 0 -#define LOG_TAG "TimedTextInBandSource" +#define LOG_TAG "TimedText3GPPSource" #include <utils/Log.h> #include <binder/Parcel.h> @@ -26,19 +26,19 @@ #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> -#include "TimedTextInBandSource.h" +#include "TimedText3GPPSource.h" #include "TextDescriptions.h" namespace android { -TimedTextInBandSource::TimedTextInBandSource(const sp<MediaSource>& mediaSource) +TimedText3GPPSource::TimedText3GPPSource(const sp<MediaSource>& mediaSource) : mSource(mediaSource) { } -TimedTextInBandSource::~TimedTextInBandSource() { +TimedText3GPPSource::~TimedText3GPPSource() { } -status_t TimedTextInBandSource::read( +status_t TimedText3GPPSource::read( int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options) { MediaBuffer *textBuffer = NULL; status_t err = mSource->read(&textBuffer, options); @@ -60,7 +60,7 @@ status_t TimedTextInBandSource::read( // text style for the string of text. These descriptions are present only // if they are needed. This method is used to extract the modifier // description and append it at the end of the text. -status_t TimedTextInBandSource::extractAndAppendLocalDescriptions( +status_t TimedText3GPPSource::extractAndAppendLocalDescriptions( int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel) { const void *data; size_t size = 0; @@ -68,51 +68,46 @@ status_t TimedTextInBandSource::extractAndAppendLocalDescriptions( const char *mime; CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + CHECK(strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0); - if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) { - data = textBuffer->data(); - size = textBuffer->size(); - - if (size > 0) { - parcel->freeData(); - flag |= TextDescriptions::IN_BAND_TEXT_3GPP; - return TextDescriptions::getParcelOfDescriptions( - (const uint8_t *)data, size, flag, timeUs / 1000, parcel); - } - return OK; + data = textBuffer->data(); + size = textBuffer->size(); + + if (size > 0) { + parcel->freeData(); + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, parcel); } - return ERROR_UNSUPPORTED; + return OK; } // To extract and send the global text descriptions for all the text samples // in the text track or text file. // TODO: send error message to application via notifyListener()...? -status_t TimedTextInBandSource::extractGlobalDescriptions(Parcel *parcel) { +status_t TimedText3GPPSource::extractGlobalDescriptions(Parcel *parcel) { const void *data; size_t size = 0; int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; const char *mime; CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + CHECK(strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0); + + uint32_t type; + // get the 'tx3g' box content. This box contains the text descriptions + // used to render the text track + if (!mSource->getFormat()->findData( + kKeyTextFormatData, &type, &data, &size)) { + return ERROR_MALFORMED; + } - // support 3GPP only for now - if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) { - uint32_t type; - // get the 'tx3g' box content. This box contains the text descriptions - // used to render the text track - if (!mSource->getFormat()->findData( - kKeyTextFormatData, &type, &data, &size)) { - return ERROR_MALFORMED; - } - - if (size > 0) { - flag |= TextDescriptions::IN_BAND_TEXT_3GPP; - return TextDescriptions::getParcelOfDescriptions( - (const uint8_t *)data, size, flag, 0, parcel); - } - return OK; + if (size > 0) { + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, 0, parcel); } - return ERROR_UNSUPPORTED; + return OK; } } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h index 26e5737..cb7e47c 100644 --- a/media/libstagefright/timedtext/TimedTextInBandSource.h +++ b/media/libstagefright/timedtext/TimedText3GPPSource.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef TIMED_TEXT_IN_BAND_SOURCE_H_ -#define TIMED_TEXT_IN_BAND_SOURCE_H_ +#ifndef TIMED_TEXT_3GPP_SOURCE_H_ +#define TIMED_TEXT_3GPP_SOURCE_H_ #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaSource.h> @@ -27,9 +27,9 @@ namespace android { class MediaBuffer; class Parcel; -class TimedTextInBandSource : public TimedTextSource { +class TimedText3GPPSource : public TimedTextSource { public: - TimedTextInBandSource(const sp<MediaSource>& mediaSource); + TimedText3GPPSource(const sp<MediaSource>& mediaSource); virtual status_t start() { return mSource->start(); } virtual status_t stop() { return mSource->stop(); } virtual status_t read( @@ -39,7 +39,7 @@ class TimedTextInBandSource : public TimedTextSource { virtual status_t extractGlobalDescriptions(Parcel *parcel); protected: - virtual ~TimedTextInBandSource(); + virtual ~TimedText3GPPSource(); private: sp<MediaSource> mSource; @@ -47,9 +47,9 @@ class TimedTextInBandSource : public TimedTextSource { status_t extractAndAppendLocalDescriptions( int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel); - DISALLOW_EVIL_CONSTRUCTORS(TimedTextInBandSource); + DISALLOW_EVIL_CONSTRUCTORS(TimedText3GPPSource); }; } // namespace android -#endif // TIMED_TEXT_IN_BAND_SOURCE_H_ +#endif // TIMED_TEXT_3GPP_SOURCE_H_ diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp index 9ec9415..c70870e 100644 --- a/media/libstagefright/timedtext/TimedTextDriver.cpp +++ b/media/libstagefright/timedtext/TimedTextDriver.cpp @@ -27,8 +27,7 @@ #include <media/stagefright/Utils.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/ALooper.h> - -#include "TimedTextDriver.h" +#include <media/stagefright/timedtext/TimedTextDriver.h> #include "TextDescriptions.h" #include "TimedTextPlayer.h" diff --git a/media/libstagefright/timedtext/TimedTextDriver.h b/media/libstagefright/timedtext/TimedTextDriver.h deleted file mode 100644 index efedb6e..0000000 --- a/media/libstagefright/timedtext/TimedTextDriver.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#ifndef TIMED_TEXT_DRIVER_H_ -#define TIMED_TEXT_DRIVER_H_ - -#include <media/stagefright/foundation/ABase.h> // for DISALLOW_* macro -#include <utils/Errors.h> // for status_t -#include <utils/RefBase.h> -#include <utils/threads.h> - -namespace android { - -class ALooper; -class MediaPlayerBase; -class MediaSource; -class Parcel; -class TimedTextPlayer; -class TimedTextSource; - -class TimedTextDriver { -public: - TimedTextDriver(const wp<MediaPlayerBase> &listener); - - ~TimedTextDriver(); - - // TODO: pause-resume pair seems equivalent to stop-start pair. - // Check if it is replaceable with stop-start. - status_t start(); - status_t stop(); - status_t pause(); - status_t resume(); - - status_t seekToAsync(int64_t timeUs); - - status_t addInBandTextSource(const sp<MediaSource>& source); - status_t addOutOfBandTextSource(const Parcel &request); - - status_t setTimedTextTrackIndex(int32_t index); - -private: - Mutex mLock; - - enum State { - UNINITIALIZED, - STOPPED, - PLAYING, - PAUSED, - }; - - sp<ALooper> mLooper; - sp<TimedTextPlayer> mPlayer; - wp<MediaPlayerBase> mListener; - - // Variables to be guarded by mLock. - State mState; - Vector<sp<TimedTextSource> > mTextInBandVector; - Vector<sp<TimedTextSource> > mTextOutOfBandVector; - // -- End of variables to be guarded by mLock - - status_t setTimedTextTrackIndex_l(int32_t index); - - DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver); -}; - -} // namespace android - -#endif // TIMED_TEXT_DRIVER_H_ diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index bf7cbf6..bda7b46 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -20,12 +20,12 @@ #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/timedtext/TimedTextDriver.h> #include <media/stagefright/MediaErrors.h> #include <media/MediaPlayerInterface.h> #include "TimedTextPlayer.h" -#include "TimedTextDriver.h" #include "TimedTextSource.h" namespace android { diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp index 9efe67c..ffbe1c3 100644 --- a/media/libstagefright/timedtext/TimedTextSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSource.cpp @@ -18,12 +18,15 @@ #define LOG_TAG "TimedTextSource" #include <utils/Log.h> +#include <media/stagefright/foundation/ADebug.h> // CHECK_XX macro #include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx #include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> #include "TimedTextSource.h" -#include "TimedTextInBandSource.h" +#include "TimedText3GPPSource.h" #include "TimedTextSRTSource.h" namespace android { @@ -31,7 +34,13 @@ namespace android { // static sp<TimedTextSource> TimedTextSource::CreateTimedTextSource( const sp<MediaSource>& mediaSource) { - return new TimedTextInBandSource(mediaSource); + const char *mime; + CHECK(mediaSource->getFormat()->findCString(kKeyMIMEType, &mime)); + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) { + return new TimedText3GPPSource(mediaSource); + } + ALOGE("Unsupported mime type for subtitle. : %s", mime); + return NULL; } // static diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java index e5ecd5c..95e7b5e 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java @@ -44,7 +44,9 @@ public class MediaRecorderStressTestRunner extends InstrumentationTestRunner { public static int mVideoHeight = profile.videoFrameHeight; public static int mBitRate = profile.videoBitRate; public static boolean mRemoveVideo = true; - public static int mDuration = 10000; + public static int mDuration = 10 * 1000; // 10 seconds + public static int mTimeLapseDuration = 180 * 1000; // 3 minutes + public static double mCaptureRate = 0.5; // 2 sec timelapse interval @Override public TestSuite getAllTests() { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java index 6f1959c..d15a535 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java @@ -196,7 +196,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_001 @LargeTest public void testPerformanceAddRemoveVideoItem() throws Exception { final String videoItemFileName = INPUT_FILE_PATH + @@ -241,7 +240,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_002 @LargeTest public void testPerformanceAddRemoveImageItem() throws Exception { final String imageItemFileName = INPUT_FILE_PATH + "IMG_1600x1200.jpg"; @@ -280,7 +278,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_003 @LargeTest public void testPerformanceAddRemoveTransition() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -360,7 +357,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_004 @LargeTest public void testPerformanceExport() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -541,7 +537,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_005 @LargeTest public void testPerformanceThumbnailVideoItem() throws Exception { final String videoItemFileName = INPUT_FILE_PATH @@ -574,7 +569,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_006 @LargeTest public void testPerformanceOverlayVideoItem() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -629,7 +623,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_007 @LargeTest public void testPerformanceVideoItemProperties() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -688,7 +681,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_008 @LargeTest public void testPerformanceGeneratePreviewWithTransitions() throws Exception { @@ -740,7 +732,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_009 @LargeTest public void testPerformanceWithKenBurn() throws Exception { final String videoItemFileName = INPUT_FILE_PATH + @@ -795,7 +786,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_010 @LargeTest public void testPerformanceEffectOverlappingTransition() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -864,7 +854,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_011 @LargeTest public void testPerformanceTransitionWithEffectOverlapping() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -994,7 +983,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_014 @LargeTest public void testPerformanceWithAudioTrack() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -1049,7 +1037,6 @@ public class VideoEditorPerformance extends * * @throws Exception */ - // TODO : remove PRF_015 @LargeTest public void testPerformanceAddRemoveImageItem640x480() throws Exception { final String imageItemFileName = INPUT_FILE_PATH + "IMG_640x480.jpg"; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java index e6177ba..5e649e0 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java @@ -22,11 +22,13 @@ import com.android.mediaframeworktest.MediaFrameworkTest; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; +import java.io.IOException; import java.io.Writer; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import android.hardware.Camera; +import android.media.CamcorderProfile; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.Handler; @@ -39,21 +41,21 @@ import com.android.mediaframeworktest.MediaRecorderStressTestRunner; /** * Junit / Instrumentation test case for the media player api - - */ -public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { - - + */ +public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaRecorderStressTest"; private MediaRecorder mRecorder; private Camera mCamera; - + private static final int NUMBER_OF_CAMERA_STRESS_LOOPS = 100; private static final int NUMBER_OF_RECORDER_STRESS_LOOPS = 100; private static final int NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS = 50; private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200; - private static final long WAIT_TIME_CAMERA_TEST = 3000; // 3 second - private static final long WAIT_TIME_RECORDER_TEST = 6000; // 6 second + private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 25; + private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 5* 1000; // 5 seconds + private static final long WAIT_TIME_CAMERA_TEST = 3 * 1000; // 3 seconds + private static final long WAIT_TIME_RECORDER_TEST = 6 * 1000; // 6 seconds private static final String OUTPUT_FILE = "/sdcard/temp"; private static final String OUTPUT_FILE_EXT = ".3gp"; private static final String MEDIA_STRESS_OUTPUT = @@ -61,7 +63,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback(); private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback(); - private final static int WAIT_TIMEOUT = 10000; + private final static int WAIT_TIMEOUT = 10 * 1000; // 10 seconds private Thread mLooperThread; private Handler mHandler; @@ -306,7 +308,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me } } - public void removeRecodedVideo(String filename){ + public void removeRecordedVideo(String filename){ File video = new File(filename); Log.v(TAG, "remove recorded video " + filename); video.delete(); @@ -363,6 +365,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me mRecorder.setVideoSize(video_width, video_height); mRecorder.setVideoEncoder(video_encoder); mRecorder.setAudioEncoder(audio_encoder); + mRecorder.setVideoEncodingBitRate(bit_rate); Log.v(TAG, "mediaRecorder setPreview"); mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mRecorder.prepare(); @@ -381,7 +384,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me mp.release(); validateRecordedVideo(filename); if (remove_video) { - removeRecodedVideo(filename); + removeRecordedVideo(filename); } output.write(", " + i); } @@ -392,4 +395,90 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me output.write("\n\n"); output.close(); } + + // Test case for stressing time lapse + @LargeTest + public void testStressTimeLapse() throws Exception { + SurfaceHolder mSurfaceHolder; + mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + int record_duration = MediaRecorderStressTestRunner.mTimeLapseDuration; + boolean remove_video = MediaRecorderStressTestRunner.mRemoveVideo; + double captureRate = MediaRecorderStressTestRunner.mCaptureRate; + String filename; + File stressOutFile = new File(MEDIA_STRESS_OUTPUT); + Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); + output.write("Start camera time lapse stress:\n"); + output.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n"); + + try { + output.write("No of loop: "); + for (int i = 0; i < NUMBER_OF_TIME_LAPSE_LOOPS; i++) { + filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT; + Log.v(TAG, filename); + runOnLooper(new Runnable() { + @Override + public void run() { + mRecorder = new MediaRecorder(); + } + }); + + // Set callback + mRecorder.setOnErrorListener(mRecorderErrorCallback); + + // Set video source + mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); + + // Set camcorder profile for time lapse + CamcorderProfile profile = + CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH); + mRecorder.setProfile(profile); + + // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc. + // http://developer.android.com/guide/topics/media/camera.html#time-lapse-video + mRecorder.setCaptureRate(captureRate); + + // Set output file + mRecorder.setOutputFile(filename); + + // Set the preview display + Log.v(TAG, "mediaRecorder setPreviewDisplay"); + mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); + + mRecorder.prepare(); + mRecorder.start(); + Thread.sleep(record_duration); + Log.v(TAG, "Before stop"); + mRecorder.stop(); + mRecorder.release(); + + // Start the playback + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(filename); + mp.setDisplay(mSurfaceHolder); + mp.prepare(); + mp.start(); + Thread.sleep(TIME_LAPSE_PLAYBACK_WAIT_TIME); + mp.release(); + validateRecordedVideo(filename); + if(remove_video) { + removeRecordedVideo(filename); + } + output.write(", " + i); + } + } + catch (IllegalStateException e) { + assertTrue("Camera time lapse stress test IllegalStateException", false); + Log.v(TAG, e.toString()); + } + catch (IOException e) { + assertTrue("Camera time lapse stress test IOException", false); + Log.v(TAG, e.toString()); + } + catch (Exception e) { + assertTrue("Camera time lapse stress test Exception", false); + Log.v(TAG, e.toString()); + } + output.write("\n\n"); + output.close(); + } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java index 4d30784..7784c7b 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java @@ -167,7 +167,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_001 @LargeTest public void testStressAddRemoveVideoItem() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -241,7 +240,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_002 @LargeTest public void testStressAddRemoveImageItem() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -310,7 +308,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_003 @LargeTest public void testStressAddRemoveTransition() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -428,7 +425,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_004 @LargeTest public void testStressAddRemoveOverlay() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -493,7 +489,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_005 @LargeTest public void testStressAddRemoveEffects() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -590,7 +585,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_006 @LargeTest public void testStressThumbnailVideoItem() throws Exception { final String videoItemFileName = INPUT_FILE_PATH @@ -651,7 +645,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_007 @LargeTest public void testStressMediaProperties() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -747,7 +740,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_008 @LargeTest public void testStressInsertMovieItems() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -759,7 +751,7 @@ public class VideoEditorStressTest "MPEG4_SP_640x480_15fps_1200kbps_AACLC_48khz_64kbps_m_1_17.3gp"; final String[] loggingInfo = new String[1]; int i = 0; - writeTestCaseHeader("testStressInsertMoveItems"); + writeTestCaseHeader("testStressInsertMovieItems"); final MediaVideoItem mediaItem1 = new MediaVideoItem(mVideoEditor, "m1", VideoItemFileName1, renderingMode); @@ -801,7 +793,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_009 @LargeTest public void testStressLoadAndSave() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -916,7 +907,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_010 @LargeTest public void testStressMultipleExport() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -1007,7 +997,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_011 @LargeTest public void testStressOverlayTransKenBurn() throws Exception { final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER; @@ -1094,7 +1083,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_012 @LargeTest public void testStressAudioTrackVideo() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -1147,7 +1135,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_013 @LargeTest public void testStressStoryBoard() throws Exception { final String videoItemFileName1 = INPUT_FILE_PATH + @@ -1237,7 +1224,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_014 @LargeTest public void testStressAudioTrackOnly() throws Exception { @@ -1267,7 +1253,6 @@ public class VideoEditorStressTest * * @throws Exception */ - // TODO : remove TC_STR_016 -- New Test Case @LargeTest public void testStressThumbnailImageItem() throws Exception { final String imageItemFileName = INPUT_FILE_PATH + "IMG_640x480.jpg"; |