summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/MediaFile.java6
-rw-r--r--media/java/android/mtp/MtpClient.java270
-rw-r--r--media/java/android/mtp/MtpDatabase.java1
-rw-r--r--media/java/android/mtp/MtpDevice.java141
-rw-r--r--media/java/android/mtp/MtpDeviceInfo.java72
-rw-r--r--media/java/android/mtp/MtpObjectInfo.java257
-rw-r--r--media/java/android/mtp/MtpStorageInfo.java82
-rw-r--r--media/jni/Android.mk1
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
-rw-r--r--media/jni/android_mtp_MtpDevice.cpp663
-rwxr-xr-xmedia/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp2
-rw-r--r--media/libstagefright/ACodec.cpp96
-rw-r--r--media/libstagefright/AMRExtractor.cpp125
-rw-r--r--media/libstagefright/Android.mk3
-rw-r--r--media/libstagefright/AwesomePlayer.cpp13
-rw-r--r--media/libstagefright/DataSource.cpp2
-rw-r--r--media/libstagefright/FLACExtractor.cpp813
-rw-r--r--media/libstagefright/MPEG4Extractor.cpp14
-rw-r--r--media/libstagefright/MediaDefs.cpp1
-rw-r--r--media/libstagefright/MediaExtractor.cpp3
-rw-r--r--media/libstagefright/OMXCodec.cpp2
-rw-r--r--media/libstagefright/OggExtractor.cpp38
-rw-r--r--media/libstagefright/SampleIterator.cpp2
-rw-r--r--media/libstagefright/SampleTable.cpp73
-rw-r--r--media/libstagefright/StagefrightMediaScanner.cpp2
-rw-r--r--media/libstagefright/include/AMRExtractor.h5
-rw-r--r--media/libstagefright/include/FLACExtractor.h64
-rw-r--r--media/libstagefright/include/OggExtractor.h3
-rw-r--r--media/libstagefright/include/SampleTable.h8
-rw-r--r--media/mtp/Android.mk2
-rw-r--r--media/mtp/MtpClient.cpp251
-rw-r--r--media/mtp/MtpClient.h68
-rw-r--r--media/mtp/MtpDevice.cpp264
-rw-r--r--media/mtp/MtpDevice.h14
-rw-r--r--media/mtp/MtpServer.cpp5
-rw-r--r--media/tests/CameraBrowser/Android.mk10
-rw-r--r--media/tests/CameraBrowser/AndroidManifest.xml31
-rw-r--r--media/tests/CameraBrowser/res/layout/object_info.xml169
-rw-r--r--media/tests/CameraBrowser/res/layout/object_list.xml33
-rw-r--r--media/tests/CameraBrowser/res/values/strings.xml46
-rw-r--r--media/tests/CameraBrowser/res/values/styles.xml34
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java139
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java41
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java52
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java147
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java203
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java121
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java48
48 files changed, 4033 insertions, 413 deletions
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 6a3ff7c..ee2c1e8 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -46,8 +46,9 @@ public class MediaFile {
public static final int FILE_TYPE_OGG = 7;
public static final int FILE_TYPE_AAC = 8;
public static final int FILE_TYPE_MKA = 9;
+ public static final int FILE_TYPE_FLAC = 10;
private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3;
- private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_MKA;
+ private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_FLAC;
// MIDI file types
public static final int FILE_TYPE_MID = 11;
@@ -99,8 +100,7 @@ public class MediaFile {
public static final int FILE_TYPE_MS_WORD = 104;
public static final int FILE_TYPE_MS_EXCEL = 105;
public static final int FILE_TYPE_MS_POWERPOINT = 106;
- public static final int FILE_TYPE_FLAC = 107;
- public static final int FILE_TYPE_ZIP = 108;
+ public static final int FILE_TYPE_ZIP = 107;
static class MediaFileType {
diff --git a/media/java/android/mtp/MtpClient.java b/media/java/android/mtp/MtpClient.java
new file mode 100644
index 0000000..19ee92a
--- /dev/null
+++ b/media/java/android/mtp/MtpClient.java
@@ -0,0 +1,270 @@
+/*
+ * 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.
+ */
+
+package android.mtp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.UsbConstants;
+import android.hardware.UsbDevice;
+import android.hardware.UsbInterface;
+import android.hardware.UsbManager;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class helps an application manage a list of connected MTP devices.
+ * It listens for MTP devices being attached and removed from the USB host bus
+ * and notifies the application when the MTP device list changes.
+ * {@hide}
+ */
+public class MtpClient {
+
+ private static final String TAG = "MtpClient";
+
+ private final Context mContext;
+ private final UsbManager mUsbManager;
+ private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
+ private final ArrayList<MtpDevice> mDeviceList = new ArrayList<MtpDevice>();
+
+ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String deviceName = intent.getStringExtra(UsbManager.EXTRA_DEVICE_NAME);
+
+ synchronized (mDeviceList) {
+ MtpDevice mtpDevice = getDeviceLocked(deviceName);
+
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ if (mtpDevice == null) {
+ UsbDevice usbDevice =
+ (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ mtpDevice = openDevice(usbDevice);
+ }
+ if (mtpDevice != null) {
+ mDeviceList.add(mtpDevice);
+ for (Listener listener : mListeners) {
+ listener.deviceAdded(mtpDevice);
+ }
+ }
+ } else if (mtpDevice != null) {
+ mDeviceList.remove(mtpDevice);
+ for (Listener listener : mListeners) {
+ listener.deviceRemoved(mtpDevice);
+ }
+ }
+ }
+ }
+ };
+
+ public interface Listener {
+ public void deviceAdded(MtpDevice device);
+ public void deviceRemoved(MtpDevice device);
+ }
+
+ static public boolean isCamera(UsbDevice device) {
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
+ intf.getInterfaceSubclass() == 1 &&
+ intf.getInterfaceProtocol() == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private MtpDevice openDevice(UsbDevice usbDevice) {
+ if (isCamera(usbDevice)) {
+ MtpDevice mtpDevice = new MtpDevice(usbDevice);
+ if (mtpDevice.open(mUsbManager)) {
+ return mtpDevice;
+ }
+ }
+ return null;
+ }
+
+ public MtpClient(Context context) {
+ mContext = context;
+ mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ context.registerReceiver(mUsbReceiver, filter);
+
+ for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
+ MtpDevice mtpDevice = getDeviceLocked(usbDevice.getDeviceName());
+ if (mtpDevice == null) {
+ mtpDevice = openDevice(usbDevice);
+ }
+ if (mtpDevice != null) {
+ mDeviceList.add(mtpDevice);
+ }
+ }
+ }
+
+ public void close() {
+ mContext.unregisterReceiver(mUsbReceiver);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public void addListener(Listener listener) {
+ synchronized (mDeviceList) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+ }
+
+ public void removeListener(Listener listener) {
+ synchronized (mDeviceList) {
+ mListeners.remove(listener);
+ }
+ }
+
+ public MtpDevice getDevice(String deviceName) {
+ synchronized (mDeviceList) {
+ return getDeviceLocked(deviceName);
+ }
+ }
+
+ public MtpDevice getDevice(int id) {
+ synchronized (mDeviceList) {
+ return getDeviceLocked(UsbDevice.getDeviceName(id));
+ }
+ }
+
+ private MtpDevice getDeviceLocked(String deviceName) {
+ for (MtpDevice device : mDeviceList) {
+ if (device.getDeviceName().equals(deviceName)) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ public List<MtpDevice> getDeviceList() {
+ synchronized (mDeviceList) {
+ return new ArrayList<MtpDevice>(mDeviceList);
+ }
+ }
+
+ public List<MtpStorageInfo> getStorageList(String deviceName) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return null;
+ }
+ int[] storageIds = device.getStorageIds();
+ if (storageIds == null) {
+ return null;
+ }
+
+ int length = storageIds.length;
+ ArrayList<MtpStorageInfo> storageList = new ArrayList<MtpStorageInfo>(length);
+ for (int i = 0; i < length; i++) {
+ MtpStorageInfo info = device.getStorageInfo(storageIds[i]);
+ if (info == null) {
+ Log.w(TAG, "getStorageInfo failed");
+ } else {
+ storageList.add(info);
+ }
+ }
+ return storageList;
+ }
+
+ public MtpObjectInfo getObjectInfo(String deviceName, int objectHandle) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return null;
+ }
+ return device.getObjectInfo(objectHandle);
+ }
+
+ public boolean deleteObject(String deviceName, int objectHandle) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return false;
+ }
+ return device.deleteObject(objectHandle);
+ }
+
+ public List<MtpObjectInfo> getObjectList(String deviceName, int storageId, int objectHandle) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return null;
+ }
+ if (objectHandle == 0) {
+ // all objects in root of storage
+ objectHandle = 0xFFFFFFFF;
+ }
+ int[] handles = device.getObjectHandles(storageId, 0, objectHandle);
+ if (handles == null) {
+ return null;
+ }
+
+ int length = handles.length;
+ ArrayList<MtpObjectInfo> objectList = new ArrayList<MtpObjectInfo>(length);
+ for (int i = 0; i < length; i++) {
+ MtpObjectInfo info = device.getObjectInfo(handles[i]);
+ if (info == null) {
+ Log.w(TAG, "getObjectInfo failed");
+ } else {
+ objectList.add(info);
+ }
+ }
+ return objectList;
+ }
+
+ public byte[] getObject(String deviceName, int objectHandle, int objectSize) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return null;
+ }
+ return device.getObject(objectHandle, objectSize);
+ }
+
+ public byte[] getThumbnail(String deviceName, int objectHandle) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return null;
+ }
+ return device.getThumbnail(objectHandle);
+ }
+
+ public boolean importFile(String deviceName, int objectHandle, String destPath) {
+ MtpDevice device = getDevice(deviceName);
+ if (device == null) {
+ return false;
+ }
+ return device.importFile(objectHandle, destPath);
+ }
+}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index a595562..98de2f7 100644
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -309,6 +309,7 @@ public class MtpDatabase {
MtpConstants.FORMAT_M3U_PLAYLIST,
MtpConstants.FORMAT_PLS_PLAYLIST,
MtpConstants.FORMAT_XML_DOCUMENT,
+ MtpConstants.FORMAT_FLAC,
};
}
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
new file mode 100644
index 0000000..2d726c2
--- /dev/null
+++ b/media/java/android/mtp/MtpDevice.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package android.mtp;
+
+import android.hardware.UsbDevice;
+import android.hardware.UsbManager;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * This class represents an MTP device connected on the USB host bus.
+ *
+ * {@hide}
+ */
+public final class MtpDevice {
+
+ private static final String TAG = "MtpDevice";
+
+ private final UsbDevice mDevice;
+
+ static {
+ System.loadLibrary("media_jni");
+ }
+
+ public MtpDevice(UsbDevice device) {
+ mDevice = device;
+ }
+
+ public boolean open(UsbManager manager) {
+ if (manager.openDevice(mDevice)) {
+ return native_open(mDevice.getDeviceName(), mDevice.getFileDescriptor());
+ } else {
+ return false;
+ }
+ }
+
+ public void close() {
+ Log.d(TAG, "close");
+ native_close();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ Log.d(TAG, "finalize");
+ try {
+ native_close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public String getDeviceName() {
+ return mDevice.getDeviceName();
+ }
+
+ public int getDeviceId() {
+ return mDevice.getDeviceId();
+ }
+
+ @Override
+ public String toString() {
+ return mDevice.getDeviceName();
+ }
+
+ public MtpDeviceInfo getDeviceInfo() {
+ return native_get_device_info();
+ }
+
+ public int[] getStorageIds() {
+ return native_get_storage_ids();
+ }
+
+ public int[] getObjectHandles(int storageId, int format, int objectHandle) {
+ return native_get_object_handles(storageId, format, objectHandle);
+ }
+
+ public byte[] getObject(int objectHandle, int objectSize) {
+ return native_get_object(objectHandle, objectSize);
+ }
+
+ public byte[] getThumbnail(int objectHandle) {
+ return native_get_thumbnail(objectHandle);
+ }
+
+ public MtpStorageInfo getStorageInfo(int storageId) {
+ return native_get_storage_info(storageId);
+ }
+
+ public MtpObjectInfo getObjectInfo(int objectHandle) {
+ return native_get_object_info(objectHandle);
+ }
+
+ public boolean deleteObject(int objectHandle) {
+ return native_delete_object(objectHandle);
+ }
+
+ public long getParent(int objectHandle) {
+ return native_get_parent(objectHandle);
+ }
+
+ public long getStorageID(int objectHandle) {
+ return native_get_storage_id(objectHandle);
+ }
+
+ // Reads a file from device to host to the specified destination.
+ // Returns true if the transfer succeeds.
+ public boolean importFile(int objectHandle, String destPath) {
+ return native_import_file(objectHandle, destPath);
+ }
+
+ // used by the JNI code
+ private int mNativeContext;
+
+ private native boolean native_open(String deviceName, int fd);
+ private native void native_close();
+ private native MtpDeviceInfo native_get_device_info();
+ private native int[] native_get_storage_ids();
+ private native MtpStorageInfo native_get_storage_info(int storageId);
+ private native int[] native_get_object_handles(int storageId, int format, int objectHandle);
+ private native MtpObjectInfo native_get_object_info(int objectHandle);
+ private native byte[] native_get_object(int objectHandle, int objectSize);
+ private native byte[] native_get_thumbnail(int objectHandle);
+ private native boolean native_delete_object(int objectHandle);
+ private native long native_get_parent(int objectHandle);
+ private native long native_get_storage_id(int objectHandle);
+ private native boolean native_import_file(int objectHandle, String destPath);
+}
diff --git a/media/java/android/mtp/MtpDeviceInfo.java b/media/java/android/mtp/MtpDeviceInfo.java
new file mode 100644
index 0000000..d918c20
--- /dev/null
+++ b/media/java/android/mtp/MtpDeviceInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package android.mtp;
+
+/**
+ * This class encapsulates information about an MTP device.
+ * This corresponds to the DeviceInfo Dataset described in
+ * section 5.1.1 of the MTP specification.
+ *
+ * {@hide}
+ */
+public class MtpDeviceInfo {
+
+ private String mManufacturer;
+ private String mModel;
+ private String mVersion;
+ private String mSerialNumber;
+
+ // only instantiated via JNI
+ private MtpDeviceInfo() {
+ }
+
+ /**
+ * Returns the manufacturer's name for the MTP device
+ *
+ * @return the manufacturer name
+ */
+ public final String getManufacturer() {
+ return mManufacturer;
+ }
+
+ /**
+ * Returns the model name for the MTP device
+ *
+ * @return the model name
+ */
+ public final String getModel() {
+ return mModel;
+ }
+
+ /**
+ * Returns the version string the MTP device
+ *
+ * @return the device version
+ */
+ public final String getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns the unique serial number for the MTP device
+ *
+ * @return the serial number
+ */
+ public final String getSerialNumber() {
+ return mSerialNumber;
+ }
+} \ No newline at end of file
diff --git a/media/java/android/mtp/MtpObjectInfo.java b/media/java/android/mtp/MtpObjectInfo.java
new file mode 100644
index 0000000..309d524
--- /dev/null
+++ b/media/java/android/mtp/MtpObjectInfo.java
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+package android.mtp;
+
+/**
+ * This class encapsulates information about an object on an MTP device.
+ * This corresponds to the ObjectInfo Dataset described in
+ * section 5.3.1 of the MTP specification.
+ *
+ * {@hide}
+ */
+public final class MtpObjectInfo {
+ private int mHandle;
+ private int mStorageId;
+ private int mFormat;
+ private int mProtectionStatus;
+ private int mCompressedSize;
+ private int mThumbFormat;
+ private int mThumbCompressedSize;
+ private int mThumbPixWidth;
+ private int mThumbPixHeight;
+ private int mImagePixWidth;
+ private int mImagePixHeight;
+ private int mImagePixDepth;
+ private int mParent;
+ private int mAssociationType;
+ private int mAssociationDesc;
+ private int mSequenceNumber;
+ private String mName;
+ private long mDateCreated;
+ private long mDateModified;
+ private String mKeywords;
+
+ // only instantiated via JNI
+ private MtpObjectInfo() {
+ }
+
+ /**
+ * Returns the object handle for the MTP object
+ *
+ * @return the object handle
+ */
+ public final int getObjectHandle() {
+ return mHandle;
+ }
+
+ /**
+ * Returns the storage ID for the MTP object's storage unit
+ *
+ * @return the storage ID
+ */
+ public final int getStorageId() {
+ return mStorageId;
+ }
+
+ /**
+ * Returns the format code for the MTP object
+ *
+ * @return the format code
+ */
+ public final int getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Returns the protection status for the MTP object
+ * Possible values are:
+ *
+ * <ul>
+ * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_NONE}
+ * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_READ_ONLY}
+ * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_NON_TRANSFERABLE_DATA}
+ * </ul>
+ *
+ * @return the protection status
+ */
+ public final int getProtectionStatus() {
+ return mProtectionStatus;
+ }
+
+ /**
+ * Returns the size of the MTP object
+ *
+ * @return the object size
+ */
+ public final int getCompressedSize() {
+ return mCompressedSize;
+ }
+
+ /**
+ * Returns the format code for the MTP object's thumbnail
+ * Will be zero for objects with no thumbnail
+ *
+ * @return the thumbnail format code
+ */
+ public final int getThumbFormat() {
+ return mThumbFormat;
+ }
+
+ /**
+ * Returns the size of the MTP object's thumbnail
+ * Will be zero for objects with no thumbnail
+ *
+ * @return the thumbnail size
+ */
+ public final int getThumbCompressedSize() {
+ return mThumbCompressedSize;
+ }
+
+ /**
+ * Returns the width of the MTP object's thumbnail in pixels
+ * Will be zero for objects with no thumbnail
+ *
+ * @return the thumbnail width
+ */
+ public final int getThumbPixWidth() {
+ return mThumbPixWidth;
+ }
+
+ /**
+ * Returns the height of the MTP object's thumbnail in pixels
+ * Will be zero for objects with no thumbnail
+ *
+ * @return the thumbnail height
+ */
+ public final int getThumbPixHeight() {
+ return mThumbPixHeight;
+ }
+
+ /**
+ * Returns the width of the MTP object in pixels
+ * Will be zero for non-image objects
+ *
+ * @return the image width
+ */
+ public final int getImagePixWidth() {
+ return mImagePixWidth;
+ }
+
+ /**
+ * Returns the height of the MTP object in pixels
+ * Will be zero for non-image objects
+ *
+ * @return the image height
+ */
+ public final int getImagePixHeight() {
+ return mImagePixHeight;
+ }
+
+ /**
+ * Returns the depth of the MTP object in bits per pixel
+ * Will be zero for non-image objects
+ *
+ * @return the image depth
+ */
+ public final int getImagePixDepth() {
+ return mImagePixDepth;
+ }
+
+ /**
+ * Returns the object handle for the object's parent
+ * Will be zero for the root directory of a storage unit
+ *
+ * @return the object's parent
+ */
+ public final int getParent() {
+ return mParent;
+ }
+
+ /**
+ * Returns the association type for the MTP object
+ * Will be zero objects that are not of format
+ * {@link android.mtp.MtpConstants#FORMAT_ASSOCIATION}
+ * For directories the association type is typically
+ * {@link android.mtp.MtpConstants#ASSOCIATION_TYPE_GENERIC_FOLDER}
+ *
+ * @return the object's association type
+ */
+ public final int getAssociationType() {
+ return mAssociationType;
+ }
+
+ /**
+ * Returns the association description for the MTP object
+ * Will be zero objects that are not of format
+ * {@link android.mtp.MtpConstants#FORMAT_ASSOCIATION}
+ *
+ * @return the object's association description
+ */
+ public final int getAssociationDesc() {
+ return mAssociationDesc;
+ }
+
+ /**
+ * Returns the sequence number for the MTP object
+ * This field is typically not used for MTP devices,
+ * but is sometimes used to define a sequence of photos
+ * on PTP cameras.
+ *
+ * @return the object's sequence number
+ */
+ public final int getSequenceNumber() {
+ return mSequenceNumber;
+ }
+
+ /**
+ * Returns the name of the MTP object
+ *
+ * @return the object's name
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the creation date of the MTP object
+ * The value is represented as milliseconds since January 1, 1970
+ *
+ * @return the object's creation date
+ */
+ public final long getDateCreated() {
+ return mDateCreated;
+ }
+
+ /**
+ * Returns the modification date of the MTP object
+ * The value is represented as milliseconds since January 1, 1970
+ *
+ * @return the object's modification date
+ */
+ public final long getDateModified() {
+ return mDateModified;
+ }
+
+ /**
+ * Returns a comma separated list of keywords for the MTP object
+ *
+ * @return the object's keyword list
+ */
+ public final String getKeywords() {
+ return mKeywords;
+ }
+}
diff --git a/media/java/android/mtp/MtpStorageInfo.java b/media/java/android/mtp/MtpStorageInfo.java
new file mode 100644
index 0000000..811455a
--- /dev/null
+++ b/media/java/android/mtp/MtpStorageInfo.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+package android.mtp;
+
+/**
+ * This class encapsulates information about a storage unit on an MTP device.
+ * This corresponds to the StorageInfo Dataset described in
+ * section 5.2.2 of the MTP specification.
+ *
+ * {@hide}
+ */
+public final class MtpStorageInfo {
+
+ private int mStorageId;
+ private long mMaxCapacity;
+ private long mFreeSpace;
+ private String mDescription;
+ private String mVolumeIdentifier;
+
+ // only instantiated via JNI
+ private MtpStorageInfo() {
+ }
+
+ /**
+ * Returns the storage ID for the storage unit
+ *
+ * @return the storage ID
+ */
+ public final int getStorageId() {
+ return mStorageId;
+ }
+
+ /**
+ * Returns the maximum storage capacity for the storage unit in bytes
+ *
+ * @return the maximum capacity
+ */
+ public final long getMaxCapacity() {
+ return mMaxCapacity;
+ }
+
+ /**
+ * Returns the amount of free space in the storage unit in bytes
+ *
+ * @return the amount of free space
+ */
+ public final long getFreeSpace() {
+ return mFreeSpace;
+ }
+
+ /**
+ * Returns the description string for the storage unit
+ *
+ * @return the storage unit description
+ */
+ public final String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns the volume identifier for the storage unit
+ *
+ * @return the storage volume identifier
+ */
+ public final String getVolumeIdentifier() {
+ return mVolumeIdentifier;
+ }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index ab6e512..2a89a2a 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -10,6 +10,7 @@ LOCAL_SRC_FILES:= \
android_media_MediaProfiles.cpp \
android_media_AmrInputStream.cpp \
android_mtp_MtpDatabase.cpp \
+ android_mtp_MtpDevice.cpp \
android_mtp_MtpServer.cpp \
LOCAL_SHARED_LIBRARIES := \
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index f0609b2..0884e35 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -776,6 +776,7 @@ extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_media_AmrInputStream(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
+extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
jint JNI_OnLoad(JavaVM* vm, void* reserved)
@@ -829,6 +830,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
goto bail;
}
+ if (register_android_mtp_MtpDevice(env) < 0) {
+ LOGE("ERROR: MtpDevice native registration failed");
+ goto bail;
+ }
+
if (register_android_mtp_MtpServer(env) < 0) {
LOGE("ERROR: MtpServer native registration failed");
goto bail;
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
new file mode 100644
index 0000000..9e67985
--- /dev/null
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -0,0 +1,663 @@
+/*
+ * 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 "MtpDeviceJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "private/android_filesystem_config.h"
+
+#include "MtpTypes.h"
+#include "MtpDevice.h"
+#include "MtpDeviceInfo.h"
+#include "MtpStorageInfo.h"
+#include "MtpObjectInfo.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static jfieldID field_context;
+
+jclass clazz_deviceInfo;
+jclass clazz_storageInfo;
+jclass clazz_objectInfo;
+
+jmethodID constructor_deviceInfo;
+jmethodID constructor_storageInfo;
+jmethodID constructor_objectInfo;
+
+// MtpDeviceInfo fields
+static jfieldID field_deviceInfo_manufacturer;
+static jfieldID field_deviceInfo_model;
+static jfieldID field_deviceInfo_version;
+static jfieldID field_deviceInfo_serialNumber;
+
+// MtpStorageInfo fields
+static jfieldID field_storageInfo_storageId;
+static jfieldID field_storageInfo_maxCapacity;
+static jfieldID field_storageInfo_freeSpace;
+static jfieldID field_storageInfo_description;
+static jfieldID field_storageInfo_volumeIdentifier;
+
+// MtpObjectInfo fields
+static jfieldID field_objectInfo_handle;
+static jfieldID field_objectInfo_storageId;
+static jfieldID field_objectInfo_format;
+static jfieldID field_objectInfo_protectionStatus;
+static jfieldID field_objectInfo_compressedSize;
+static jfieldID field_objectInfo_thumbFormat;
+static jfieldID field_objectInfo_thumbCompressedSize;
+static jfieldID field_objectInfo_thumbPixWidth;
+static jfieldID field_objectInfo_thumbPixHeight;
+static jfieldID field_objectInfo_imagePixWidth;
+static jfieldID field_objectInfo_imagePixHeight;
+static jfieldID field_objectInfo_imagePixDepth;
+static jfieldID field_objectInfo_parent;
+static jfieldID field_objectInfo_associationType;
+static jfieldID field_objectInfo_associationDesc;
+static jfieldID field_objectInfo_sequenceNumber;
+static jfieldID field_objectInfo_name;
+static jfieldID field_objectInfo_dateCreated;
+static jfieldID field_objectInfo_dateModified;
+static jfieldID field_objectInfo_keywords;
+
+#ifdef HAVE_ANDROID_OS
+
+MtpDevice* get_device_from_object(JNIEnv* env, jobject javaDevice)
+{
+ return (MtpDevice*)env->GetIntField(javaDevice, field_context);
+}
+
+static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+ if (env->ExceptionCheck()) {
+ LOGE("An exception was thrown by callback '%s'.", methodName);
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+#endif // HAVE_ANDROID_OS
+
+// ----------------------------------------------------------------------------
+
+static jboolean
+android_mtp_MtpDevice_open(JNIEnv *env, jobject thiz, jstring deviceName, jint fd)
+{
+#ifdef HAVE_ANDROID_OS
+ LOGD("open\n");
+ const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);
+ MtpDevice* device = MtpDevice::open(deviceNameStr, fd);
+ env->ReleaseStringUTFChars(deviceName, deviceNameStr);
+
+ if (device)
+ env->SetIntField(thiz, field_context, (int)device);
+ return (device != NULL);
+#endif
+}
+
+static void
+android_mtp_MtpDevice_close(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+ LOGD("close\n");
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (device) {
+ device->close();
+ delete device;
+ env->SetIntField(thiz, field_context, 0);
+ }
+#endif
+}
+
+static jobject
+android_mtp_MtpDevice_get_device_info(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device) {
+ LOGD("android_mtp_MtpDevice_get_device_info device is null");
+ return NULL;
+ }
+ MtpDeviceInfo* deviceInfo = device->getDeviceInfo();
+ if (!deviceInfo) {
+ LOGD("android_mtp_MtpDevice_get_device_info deviceInfo is null");
+ return NULL;
+ }
+ jobject info = env->NewObject(clazz_deviceInfo, constructor_deviceInfo);
+ if (info == NULL) {
+ LOGE("Could not create a MtpDeviceInfo object");
+ delete deviceInfo;
+ return NULL;
+ }
+
+ if (deviceInfo->mManufacturer)
+ env->SetObjectField(info, field_deviceInfo_manufacturer,
+ env->NewStringUTF(deviceInfo->mManufacturer));
+ if (deviceInfo->mModel)
+ env->SetObjectField(info, field_deviceInfo_model,
+ env->NewStringUTF(deviceInfo->mModel));
+ if (deviceInfo->mVersion)
+ env->SetObjectField(info, field_deviceInfo_version,
+ env->NewStringUTF(deviceInfo->mVersion));
+ if (deviceInfo->mSerial)
+ env->SetObjectField(info, field_deviceInfo_serialNumber,
+ env->NewStringUTF(deviceInfo->mSerial));
+
+ delete deviceInfo;
+ return info;
+#else
+ return NULL;
+#endif
+}
+
+static jintArray
+android_mtp_MtpDevice_get_storage_ids(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+ MtpStorageIDList* storageIDs = device->getStorageIDs();
+ if (!storageIDs)
+ return NULL;
+
+ int length = storageIDs->size();
+ jintArray array = env->NewIntArray(length);
+ // FIXME is this cast safe?
+ env->SetIntArrayRegion(array, 0, length, (const jint *)storageIDs->array());
+
+ delete storageIDs;
+ return array;
+#else
+ return NULL;
+#endif
+}
+
+static jobject
+android_mtp_MtpDevice_get_storage_info(JNIEnv *env, jobject thiz, jint storageID)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+ MtpStorageInfo* storageInfo = device->getStorageInfo(storageID);
+ if (!storageInfo)
+ return NULL;
+
+ jobject info = env->NewObject(clazz_storageInfo, constructor_storageInfo);
+ if (info == NULL) {
+ LOGE("Could not create a MtpStorageInfo object");
+ delete storageInfo;
+ return NULL;
+ }
+
+ if (storageInfo->mStorageID)
+ env->SetIntField(info, field_storageInfo_storageId, storageInfo->mStorageID);
+ if (storageInfo->mMaxCapacity)
+ env->SetLongField(info, field_storageInfo_maxCapacity, storageInfo->mMaxCapacity);
+ if (storageInfo->mFreeSpaceBytes)
+ env->SetLongField(info, field_storageInfo_freeSpace, storageInfo->mFreeSpaceBytes);
+ if (storageInfo->mStorageDescription)
+ env->SetObjectField(info, field_storageInfo_description,
+ env->NewStringUTF(storageInfo->mStorageDescription));
+ if (storageInfo->mVolumeIdentifier)
+ env->SetObjectField(info, field_storageInfo_volumeIdentifier,
+ env->NewStringUTF(storageInfo->mVolumeIdentifier));
+
+ delete storageInfo;
+ return info;
+#else
+ return NULL;
+#endif
+}
+
+static jintArray
+android_mtp_MtpDevice_get_object_handles(JNIEnv *env, jobject thiz,
+ jint storageID, jint format, jint objectID)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+ MtpObjectHandleList* handles = device->getObjectHandles(storageID, format, objectID);
+ if (!handles)
+ return NULL;
+
+ int length = handles->size();
+ jintArray array = env->NewIntArray(length);
+ // FIXME is this cast safe?
+ env->SetIntArrayRegion(array, 0, length, (const jint *)handles->array());
+
+ delete handles;
+ return array;
+#else
+ return NULL;
+#endif
+}
+
+static jobject
+android_mtp_MtpDevice_get_object_info(JNIEnv *env, jobject thiz, jint objectID)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+ MtpObjectInfo* objectInfo = device->getObjectInfo(objectID);
+ if (!objectInfo)
+ return NULL;
+ jobject info = env->NewObject(clazz_objectInfo, constructor_objectInfo);
+ if (info == NULL) {
+ LOGE("Could not create a MtpObjectInfo object");
+ delete objectInfo;
+ return NULL;
+ }
+
+ if (objectInfo->mHandle)
+ env->SetIntField(info, field_objectInfo_handle, objectInfo->mHandle);
+ if (objectInfo->mStorageID)
+ env->SetIntField(info, field_objectInfo_storageId, objectInfo->mStorageID);
+ if (objectInfo->mFormat)
+ env->SetIntField(info, field_objectInfo_format, objectInfo->mFormat);
+ if (objectInfo->mProtectionStatus)
+ env->SetIntField(info, field_objectInfo_protectionStatus, objectInfo->mProtectionStatus);
+ if (objectInfo->mCompressedSize)
+ env->SetIntField(info, field_objectInfo_compressedSize, objectInfo->mCompressedSize);
+ if (objectInfo->mThumbFormat)
+ env->SetIntField(info, field_objectInfo_thumbFormat, objectInfo->mThumbFormat);
+ if (objectInfo->mThumbCompressedSize)
+ env->SetIntField(info, field_objectInfo_thumbCompressedSize, objectInfo->mThumbCompressedSize);
+ if (objectInfo->mThumbPixWidth)
+ env->SetIntField(info, field_objectInfo_thumbPixWidth, objectInfo->mThumbPixWidth);
+ if (objectInfo->mThumbPixHeight)
+ env->SetIntField(info, field_objectInfo_thumbPixHeight, objectInfo->mThumbPixHeight);
+ if (objectInfo->mImagePixWidth)
+ env->SetIntField(info, field_objectInfo_imagePixWidth, objectInfo->mImagePixWidth);
+ if (objectInfo->mImagePixHeight)
+ env->SetIntField(info, field_objectInfo_imagePixHeight, objectInfo->mImagePixHeight);
+ if (objectInfo->mImagePixDepth)
+ env->SetIntField(info, field_objectInfo_imagePixDepth, objectInfo->mImagePixDepth);
+ if (objectInfo->mParent)
+ env->SetIntField(info, field_objectInfo_parent, objectInfo->mParent);
+ if (objectInfo->mAssociationType)
+ env->SetIntField(info, field_objectInfo_associationType, objectInfo->mAssociationType);
+ if (objectInfo->mAssociationDesc)
+ env->SetIntField(info, field_objectInfo_associationDesc, objectInfo->mAssociationDesc);
+ if (objectInfo->mSequenceNumber)
+ env->SetIntField(info, field_objectInfo_sequenceNumber, objectInfo->mSequenceNumber);
+ if (objectInfo->mName)
+ env->SetObjectField(info, field_objectInfo_name, env->NewStringUTF(objectInfo->mName));
+ if (objectInfo->mDateCreated)
+ env->SetLongField(info, field_objectInfo_dateCreated, objectInfo->mDateCreated);
+ if (objectInfo->mDateModified)
+ env->SetLongField(info, field_objectInfo_dateModified, objectInfo->mDateModified);
+ if (objectInfo->mKeywords)
+ env->SetObjectField(info, field_objectInfo_keywords,
+ env->NewStringUTF(objectInfo->mKeywords));
+
+ delete objectInfo;
+ return info;
+#else
+ return NULL;
+#endif
+}
+
+struct get_object_callback_data {
+ JNIEnv *env;
+ jbyteArray array;
+};
+
+static bool get_object_callback(void* data, int offset, int length, void* clientData)
+{
+ get_object_callback_data* cbData = (get_object_callback_data *)clientData;
+ cbData->env->SetByteArrayRegion(cbData->array, offset, length, (jbyte *)data);
+ return true;
+}
+
+static jbyteArray
+android_mtp_MtpDevice_get_object(JNIEnv *env, jobject thiz, jint objectID, jint objectSize)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+
+ jbyteArray array = env->NewByteArray(objectSize);
+ if (!array) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return NULL;
+ }
+
+ get_object_callback_data data;
+ data.env = env;
+ data.array = array;
+
+ if (device->readObject(objectID, get_object_callback, objectSize, &data))
+ return array;
+#endif
+ return NULL;
+}
+
+static jbyteArray
+android_mtp_MtpDevice_get_thumbnail(JNIEnv *env, jobject thiz, jint objectID)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (!device)
+ return NULL;
+
+ int length;
+ void* thumbnail = device->getThumbnail(objectID, length);
+ if (! thumbnail)
+ return NULL;
+ jbyteArray array = env->NewByteArray(length);
+ env->SetByteArrayRegion(array, 0, length, (const jbyte *)thumbnail);
+
+ free(thumbnail);
+ return array;
+#else
+ return NULL;
+#endif
+}
+
+static jboolean
+android_mtp_MtpDevice_delete_object(JNIEnv *env, jobject thiz, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (device)
+ return device->deleteObject(object_id);
+ else
+ #endif
+ return NULL;
+}
+
+static jlong
+android_mtp_MtpDevice_get_parent(JNIEnv *env, jobject thiz, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (device)
+ return device->getParent(object_id);
+ else
+#endif
+ return -1;
+}
+
+static jlong
+android_mtp_MtpDevice_get_storage_id(JNIEnv *env, jobject thiz, jint object_id)
+{
+ #ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (device)
+ return device->getStorageID(object_id);
+ else
+#endif
+ return -1;
+}
+
+static jboolean
+android_mtp_MtpDevice_import_file(JNIEnv *env, jobject thiz, jint object_id, jstring dest_path)
+{
+#ifdef HAVE_ANDROID_OS
+ MtpDevice* device = get_device_from_object(env, thiz);
+ if (device) {
+ const char *destPathStr = env->GetStringUTFChars(dest_path, NULL);
+ bool result = device->readObject(object_id, destPathStr, AID_SDCARD_RW, 0664);
+ env->ReleaseStringUTFChars(dest_path, destPathStr);
+ return result;
+ }
+#endif
+ return NULL;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+ {"native_open", "(Ljava/lang/String;I)Z",
+ (void *)android_mtp_MtpDevice_open},
+ {"native_close", "()V", (void *)android_mtp_MtpDevice_close},
+ {"native_get_device_info", "()Landroid/mtp/MtpDeviceInfo;",
+ (void *)android_mtp_MtpDevice_get_device_info},
+ {"native_get_storage_ids", "()[I", (void *)android_mtp_MtpDevice_get_storage_ids},
+ {"native_get_storage_info", "(I)Landroid/mtp/MtpStorageInfo;",
+ (void *)android_mtp_MtpDevice_get_storage_info},
+ {"native_get_object_handles","(III)[I",
+ (void *)android_mtp_MtpDevice_get_object_handles},
+ {"native_get_object_info", "(I)Landroid/mtp/MtpObjectInfo;",
+ (void *)android_mtp_MtpDevice_get_object_info},
+ {"native_get_object", "(II)[B",(void *)android_mtp_MtpDevice_get_object},
+ {"native_get_thumbnail", "(I)[B",(void *)android_mtp_MtpDevice_get_thumbnail},
+ {"native_delete_object", "(I)Z", (void *)android_mtp_MtpDevice_delete_object},
+ {"native_get_parent", "(I)J", (void *)android_mtp_MtpDevice_get_parent},
+ {"native_get_storage_id", "(I)J", (void *)android_mtp_MtpDevice_get_storage_id},
+ {"native_import_file", "(ILjava/lang/String;)Z",
+ (void *)android_mtp_MtpDevice_import_file},
+};
+
+static const char* const kClassPathName = "android/mtp/MtpDevice";
+
+int register_android_mtp_MtpDevice(JNIEnv *env)
+{
+ jclass clazz;
+
+ LOGD("register_android_mtp_MtpDevice\n");
+
+ clazz = env->FindClass("android/mtp/MtpDeviceInfo");
+ if (clazz == NULL) {
+ LOGE("Can't find android/mtp/MtpDeviceInfo");
+ return -1;
+ }
+ constructor_deviceInfo = env->GetMethodID(clazz, "<init>", "()V");
+ if (constructor_deviceInfo == NULL) {
+ LOGE("Can't find android/mtp/MtpDeviceInfo constructor");
+ return -1;
+ }
+ field_deviceInfo_manufacturer = env->GetFieldID(clazz, "mManufacturer", "Ljava/lang/String;");
+ if (field_deviceInfo_manufacturer == NULL) {
+ LOGE("Can't find MtpDeviceInfo.mManufacturer");
+ return -1;
+ }
+ field_deviceInfo_model = env->GetFieldID(clazz, "mModel", "Ljava/lang/String;");
+ if (field_deviceInfo_model == NULL) {
+ LOGE("Can't find MtpDeviceInfo.mModel");
+ return -1;
+ }
+ field_deviceInfo_version = env->GetFieldID(clazz, "mVersion", "Ljava/lang/String;");
+ if (field_deviceInfo_version == NULL) {
+ LOGE("Can't find MtpDeviceInfo.mVersion");
+ return -1;
+ }
+ field_deviceInfo_serialNumber = env->GetFieldID(clazz, "mSerialNumber", "Ljava/lang/String;");
+ if (field_deviceInfo_serialNumber == NULL) {
+ LOGE("Can't find MtpDeviceInfo.mSerialNumber");
+ return -1;
+ }
+ clazz_deviceInfo = clazz;
+
+ clazz = env->FindClass("android/mtp/MtpStorageInfo");
+ if (clazz == NULL) {
+ LOGE("Can't find android/mtp/MtpStorageInfo");
+ return -1;
+ }
+ constructor_storageInfo = env->GetMethodID(clazz, "<init>", "()V");
+ if (constructor_storageInfo == NULL) {
+ LOGE("Can't find android/mtp/MtpStorageInfo constructor");
+ return -1;
+ }
+ field_storageInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I");
+ if (field_storageInfo_storageId == NULL) {
+ LOGE("Can't find MtpStorageInfo.mStorageId");
+ return -1;
+ }
+ field_storageInfo_maxCapacity = env->GetFieldID(clazz, "mMaxCapacity", "J");
+ if (field_storageInfo_maxCapacity == NULL) {
+ LOGE("Can't find MtpStorageInfo.mMaxCapacity");
+ return -1;
+ }
+ field_storageInfo_freeSpace = env->GetFieldID(clazz, "mFreeSpace", "J");
+ if (field_storageInfo_freeSpace == NULL) {
+ LOGE("Can't find MtpStorageInfo.mFreeSpace");
+ return -1;
+ }
+ field_storageInfo_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;");
+ if (field_storageInfo_description == NULL) {
+ LOGE("Can't find MtpStorageInfo.mDescription");
+ return -1;
+ }
+ field_storageInfo_volumeIdentifier = env->GetFieldID(clazz, "mVolumeIdentifier", "Ljava/lang/String;");
+ if (field_storageInfo_volumeIdentifier == NULL) {
+ LOGE("Can't find MtpStorageInfo.mVolumeIdentifier");
+ return -1;
+ }
+ clazz_storageInfo = clazz;
+
+ clazz = env->FindClass("android/mtp/MtpObjectInfo");
+ if (clazz == NULL) {
+ LOGE("Can't find android/mtp/MtpObjectInfo");
+ return -1;
+ }
+ constructor_objectInfo = env->GetMethodID(clazz, "<init>", "()V");
+ if (constructor_objectInfo == NULL) {
+ LOGE("Can't find android/mtp/MtpObjectInfo constructor");
+ return -1;
+ }
+ field_objectInfo_handle = env->GetFieldID(clazz, "mHandle", "I");
+ if (field_objectInfo_handle == NULL) {
+ LOGE("Can't find MtpObjectInfo.mHandle");
+ return -1;
+ }
+ field_objectInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I");
+ if (field_objectInfo_storageId == NULL) {
+ LOGE("Can't find MtpObjectInfo.mStorageId");
+ return -1;
+ }
+ field_objectInfo_format = env->GetFieldID(clazz, "mFormat", "I");
+ if (field_objectInfo_format == NULL) {
+ LOGE("Can't find MtpObjectInfo.mFormat");
+ return -1;
+ }
+ field_objectInfo_protectionStatus = env->GetFieldID(clazz, "mProtectionStatus", "I");
+ if (field_objectInfo_protectionStatus == NULL) {
+ LOGE("Can't find MtpObjectInfo.mProtectionStatus");
+ return -1;
+ }
+ field_objectInfo_compressedSize = env->GetFieldID(clazz, "mCompressedSize", "I");
+ if (field_objectInfo_compressedSize == NULL) {
+ LOGE("Can't find MtpObjectInfo.mCompressedSize");
+ return -1;
+ }
+ field_objectInfo_thumbFormat = env->GetFieldID(clazz, "mThumbFormat", "I");
+ if (field_objectInfo_thumbFormat == NULL) {
+ LOGE("Can't find MtpObjectInfo.mThumbFormat");
+ return -1;
+ }
+ field_objectInfo_thumbCompressedSize = env->GetFieldID(clazz, "mThumbCompressedSize", "I");
+ if (field_objectInfo_thumbCompressedSize == NULL) {
+ LOGE("Can't find MtpObjectInfo.mThumbCompressedSize");
+ return -1;
+ }
+ field_objectInfo_thumbPixWidth = env->GetFieldID(clazz, "mThumbPixWidth", "I");
+ if (field_objectInfo_thumbPixWidth == NULL) {
+ LOGE("Can't find MtpObjectInfo.mThumbPixWidth");
+ return -1;
+ }
+ field_objectInfo_thumbPixHeight = env->GetFieldID(clazz, "mThumbPixHeight", "I");
+ if (field_objectInfo_thumbPixHeight == NULL) {
+ LOGE("Can't find MtpObjectInfo.mThumbPixHeight");
+ return -1;
+ }
+ field_objectInfo_imagePixWidth = env->GetFieldID(clazz, "mImagePixWidth", "I");
+ if (field_objectInfo_imagePixWidth == NULL) {
+ LOGE("Can't find MtpObjectInfo.mImagePixWidth");
+ return -1;
+ }
+ field_objectInfo_imagePixHeight = env->GetFieldID(clazz, "mImagePixHeight", "I");
+ if (field_objectInfo_imagePixHeight == NULL) {
+ LOGE("Can't find MtpObjectInfo.mImagePixHeight");
+ return -1;
+ }
+ field_objectInfo_imagePixDepth = env->GetFieldID(clazz, "mImagePixDepth", "I");
+ if (field_objectInfo_imagePixDepth == NULL) {
+ LOGE("Can't find MtpObjectInfo.mImagePixDepth");
+ return -1;
+ }
+ field_objectInfo_parent = env->GetFieldID(clazz, "mParent", "I");
+ if (field_objectInfo_parent == NULL) {
+ LOGE("Can't find MtpObjectInfo.mParent");
+ return -1;
+ }
+ field_objectInfo_associationType = env->GetFieldID(clazz, "mAssociationType", "I");
+ if (field_objectInfo_associationType == NULL) {
+ LOGE("Can't find MtpObjectInfo.mAssociationType");
+ return -1;
+ }
+ field_objectInfo_associationDesc = env->GetFieldID(clazz, "mAssociationDesc", "I");
+ if (field_objectInfo_associationDesc == NULL) {
+ LOGE("Can't find MtpObjectInfo.mAssociationDesc");
+ return -1;
+ }
+ field_objectInfo_sequenceNumber = env->GetFieldID(clazz, "mSequenceNumber", "I");
+ if (field_objectInfo_sequenceNumber == NULL) {
+ LOGE("Can't find MtpObjectInfo.mSequenceNumber");
+ return -1;
+ }
+ field_objectInfo_name = env->GetFieldID(clazz, "mName", "Ljava/lang/String;");
+ if (field_objectInfo_name == NULL) {
+ LOGE("Can't find MtpObjectInfo.mName");
+ return -1;
+ }
+ field_objectInfo_dateCreated = env->GetFieldID(clazz, "mDateCreated", "J");
+ if (field_objectInfo_dateCreated == NULL) {
+ LOGE("Can't find MtpObjectInfo.mDateCreated");
+ return -1;
+ }
+ field_objectInfo_dateModified = env->GetFieldID(clazz, "mDateModified", "J");
+ if (field_objectInfo_dateModified == NULL) {
+ LOGE("Can't find MtpObjectInfo.mDateModified");
+ return -1;
+ }
+ field_objectInfo_keywords = env->GetFieldID(clazz, "mKeywords", "Ljava/lang/String;");
+ if (field_objectInfo_keywords == NULL) {
+ LOGE("Can't find MtpObjectInfo.mKeywords");
+ return -1;
+ }
+ clazz_objectInfo = clazz;
+
+ clazz = env->FindClass("android/mtp/MtpDevice");
+ if (clazz == NULL) {
+ LOGE("Can't find android/mtp/MtpDevice");
+ return -1;
+ }
+ field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+ if (field_context == NULL) {
+ LOGE("Can't find MtpDevice.mNativeContext");
+ return -1;
+ }
+
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/mtp/MtpDevice", gMethods, NELEM(gMethods));
+}
diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
index 26c5aca..9097e20 100755
--- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
+++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
@@ -166,7 +166,7 @@ enum {
REVERB_VOLUME_RAMP,
};
-#define REVERB_DEFAULT_PRESET REVERB_PRESET_MEDIUMROOM
+#define REVERB_DEFAULT_PRESET REVERB_PRESET_NONE
#define REVERB_SEND_LEVEL (0x0C00) // 0.75 in 4.12 format
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index dfb4e00..505d9d4 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -197,6 +197,9 @@ struct ACodec::ExecutingState : public ACodec::BaseState {
// to fill with data.
void resume();
+ // Returns true iff input and output buffers are in play.
+ bool active() const { return mActive; }
+
protected:
virtual PortMode getPortMode(OMX_U32 portIndex);
virtual bool onMessageReceived(const sp<AMessage> &msg);
@@ -205,6 +208,8 @@ protected:
virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2);
private:
+ bool mActive;
+
DISALLOW_EVIL_CONSTRUCTORS(ExecutingState);
};
@@ -564,13 +569,17 @@ status_t ACodec::freeBuffersOnPort(OMX_U32 portIndex) {
return OK;
}
-status_t ACodec::freeOutputBuffersOwnedByNativeWindow() {
+status_t ACodec::freeOutputBuffersNotOwnedByComponent() {
for (size_t i = mBuffers[kPortIndexOutput].size(); i-- > 0;) {
BufferInfo *info =
&mBuffers[kPortIndexOutput].editItemAt(i);
- if (info->mStatus ==
- BufferInfo::OWNED_BY_NATIVE_WINDOW) {
+ if (info->mStatus !=
+ BufferInfo::OWNED_BY_COMPONENT) {
+ // We shouldn't have sent out any buffers to the client at this
+ // point.
+ CHECK_NE((int)info->mStatus, (int)BufferInfo::OWNED_BY_DOWNSTREAM);
+
CHECK_EQ((status_t)OK, freeBuffer(kPortIndexOutput, i));
}
}
@@ -1195,6 +1204,9 @@ bool ACodec::BaseState::onOMXEvent(
}
bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID) {
+ LOGV("[%s] onOMXEmptyBufferDone %p",
+ mCodec->mComponentName.c_str(), bufferID);
+
BufferInfo *info =
mCodec->findBufferByID(kPortIndexInput, bufferID);
@@ -1295,7 +1307,7 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
}
if (buffer != info->mData) {
- if (!(flags & OMX_BUFFERFLAG_CODECCONFIG)) {
+ if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) {
LOGV("[%s] Needs to copy input data.",
mCodec->mComponentName.c_str());
}
@@ -1304,6 +1316,9 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
memcpy(info->mData->data(), buffer->data(), buffer->size());
}
+ LOGV("[%s] calling emptyBuffer %p",
+ mCodec->mComponentName.c_str(), bufferID);
+
CHECK_EQ(mCodec->mOMX->emptyBuffer(
mCodec->mNode,
bufferID,
@@ -1320,6 +1335,9 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
LOGV("[%s] Signalling EOS on the input port",
mCodec->mComponentName.c_str());
+ LOGV("[%s] calling emptyBuffer %p",
+ mCodec->mComponentName.c_str(), bufferID);
+
CHECK_EQ(mCodec->mOMX->emptyBuffer(
mCodec->mNode,
bufferID,
@@ -1378,6 +1396,9 @@ bool ACodec::BaseState::onOMXFillBufferDone(
int64_t timeUs,
void *platformPrivate,
void *dataPtr) {
+ LOGV("[%s] onOMXFillBufferDone %p",
+ mCodec->mComponentName.c_str(), bufferID);
+
ssize_t index;
BufferInfo *info =
mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
@@ -1396,6 +1417,9 @@ bool ACodec::BaseState::onOMXFillBufferDone(
{
if (rangeLength == 0) {
if (!(flags & OMX_BUFFERFLAG_EOS)) {
+ LOGV("[%s] calling fillBuffer %p",
+ mCodec->mComponentName.c_str(), info->mBufferID);
+
CHECK_EQ(mCodec->mOMX->fillBuffer(
mCodec->mNode, info->mBufferID),
(status_t)OK);
@@ -1503,6 +1527,9 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) {
info = mCodec->dequeueBufferFromNativeWindow();
}
+ LOGV("[%s] calling fillBuffer %p",
+ mCodec->mComponentName.c_str(), info->mBufferID);
+
CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID),
(status_t)OK);
@@ -1600,6 +1627,9 @@ void ACodec::UninitializedState::onSetup(
mCodec->mOMX = omx;
mCodec->mNode = node;
+ mCodec->mPortEOS[kPortIndexInput] =
+ mCodec->mPortEOS[kPortIndexOutput] = false;
+
mCodec->configureCodec(mime.c_str(), msg);
sp<RefBase> obj;
@@ -1717,7 +1747,8 @@ bool ACodec::IdleToExecutingState::onOMXEvent(
////////////////////////////////////////////////////////////////////////////////
ACodec::ExecutingState::ExecutingState(ACodec *codec)
- : BaseState(codec) {
+ : BaseState(codec),
+ mActive(false) {
}
ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode(
@@ -1745,6 +1776,9 @@ void ACodec::ExecutingState::submitOutputBuffers() {
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
}
+ LOGV("[%s] calling fillBuffer %p",
+ mCodec->mComponentName.c_str(), info->mBufferID);
+
CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID),
(status_t)OK);
@@ -1753,6 +1787,13 @@ void ACodec::ExecutingState::submitOutputBuffers() {
}
void ACodec::ExecutingState::resume() {
+ if (mActive) {
+ LOGV("[%s] We're already active, no need to resume.",
+ mCodec->mComponentName.c_str());
+
+ return;
+ }
+
submitOutputBuffers();
// Post the first input buffer.
@@ -1760,6 +1801,8 @@ void ACodec::ExecutingState::resume() {
BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(0);
postFillThisBuffer(info);
+
+ mActive = true;
}
void ACodec::ExecutingState::stateEntered() {
@@ -1774,6 +1817,8 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatShutdown:
{
+ mActive = false;
+
CHECK_EQ(mCodec->mOMX->sendCommand(
mCodec->mNode, OMX_CommandStateSet, OMX_StateIdle),
(status_t)OK);
@@ -1786,6 +1831,8 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) {
case kWhatFlush:
{
+ mActive = false;
+
CHECK_EQ(mCodec->mOMX->sendCommand(
mCodec->mNode, OMX_CommandFlush, OMX_ALL),
(status_t)OK);
@@ -1825,10 +1872,7 @@ bool ACodec::ExecutingState::onOMXEvent(
OMX_CommandPortDisable, kPortIndexOutput),
(status_t)OK);
- if (mCodec->mNativeWindow != NULL) {
- CHECK_EQ((status_t)OK,
- mCodec->freeOutputBuffersOwnedByNativeWindow());
- }
+ mCodec->freeOutputBuffersNotOwnedByComponent();
mCodec->changeState(mCodec->mOutputPortSettingsChangedState);
} else if (data2 == OMX_IndexConfigCommonOutputCrop) {
@@ -1876,7 +1920,12 @@ bool ACodec::OutputPortSettingsChangedState::onMessageReceived(
switch (msg->what()) {
case kWhatFlush:
case kWhatShutdown:
+ case kWhatResume:
{
+ if (msg->what() == kWhatResume) {
+ LOGV("[%s] Deferring resume", mCodec->mComponentName.c_str());
+ }
+
mCodec->deferMessage(msg);
handled = true;
break;
@@ -1925,7 +1974,10 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent(
LOGV("[%s] Output port now reenabled.",
mCodec->mComponentName.c_str());
- mCodec->mExecutingState->submitOutputBuffers();
+ if (mCodec->mExecutingState->active()) {
+ mCodec->mExecutingState->submitOutputBuffers();
+ }
+
mCodec->changeState(mCodec->mExecutingState);
return true;
@@ -1992,6 +2044,13 @@ bool ACodec::ExecutingToIdleState::onOMXEvent(
return true;
}
+ case OMX_EventPortSettingsChanged:
+ case OMX_EventBufferFlag:
+ {
+ // We're shutting down and don't care about this anymore.
+ return true;
+ }
+
default:
return BaseState::onOMXEvent(event, data1, data2);
}
@@ -2170,6 +2229,23 @@ bool ACodec::FlushingState::onOMXEvent(
return true;
}
+ case OMX_EventPortSettingsChanged:
+ {
+ sp<AMessage> msg = new AMessage(kWhatOMXMessage, mCodec->id());
+ msg->setInt32("type", omx_message::EVENT);
+ msg->setPointer("node", mCodec->mNode);
+ msg->setInt32("event", event);
+ msg->setInt32("data1", data1);
+ msg->setInt32("data2", data2);
+
+ LOGV("[%s] Deferring OMX_EventPortSettingsChanged",
+ mCodec->mComponentName.c_str());
+
+ mCodec->deferMessage(msg);
+
+ return true;
+ }
+
default:
return BaseState::onOMXEvent(event, data1, data2);
}
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index ac87c29..7eca5e4 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -35,8 +35,9 @@ class AMRSource : public MediaSource {
public:
AMRSource(const sp<DataSource> &source,
const sp<MetaData> &meta,
- size_t frameSize,
- bool isWide);
+ bool isWide,
+ const off64_t *offset_table,
+ size_t offset_table_length);
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
@@ -52,7 +53,6 @@ protected:
private:
sp<DataSource> mDataSource;
sp<MetaData> mMeta;
- size_t mFrameSize;
bool mIsWide;
off64_t mOffset;
@@ -60,6 +60,9 @@ private:
bool mStarted;
MediaBufferGroup *mGroup;
+ off64_t mOffsetTable[OFFSET_TABLE_LEN];
+ size_t mOffsetTableLength;
+
AMRSource(const AMRSource &);
AMRSource &operator=(const AMRSource &);
};
@@ -67,13 +70,25 @@ private:
////////////////////////////////////////////////////////////////////////////////
static size_t getFrameSize(bool isWide, unsigned FT) {
- static const size_t kFrameSizeNB[8] = {
- 95, 103, 118, 134, 148, 159, 204, 244
+ static const size_t kFrameSizeNB[16] = {
+ 95, 103, 118, 134, 148, 159, 204, 244,
+ 39, 43, 38, 37, // SID
+ 0, 0, 0, // future use
+ 0 // no data
};
- static const size_t kFrameSizeWB[9] = {
- 132, 177, 253, 285, 317, 365, 397, 461, 477
+ static const size_t kFrameSizeWB[16] = {
+ 132, 177, 253, 285, 317, 365, 397, 461, 477,
+ 40, // SID
+ 0, 0, 0, 0, // future use
+ 0, // speech lost
+ 0 // no data
};
+ if (FT > 15 || (isWide && FT > 9 && FT < 14) || (!isWide && FT > 11 && FT < 15)) {
+ LOGE("illegal AMR frame type %d", FT);
+ return 0;
+ }
+
size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
// Round up bits to bytes and add 1 for the header byte.
@@ -82,9 +97,26 @@ static size_t getFrameSize(bool isWide, unsigned FT) {
return frameSize;
}
+static status_t getFrameSizeByOffset(const sp<DataSource> &source,
+ off64_t offset, bool isWide, size_t *frameSize) {
+ uint8_t header;
+ if (source->readAt(offset, &header, 1) < 1) {
+ return ERROR_IO;
+ }
+
+ unsigned FT = (header >> 3) & 0x0f;
+
+ *frameSize = getFrameSize(isWide, FT);
+ if (*frameSize == 0) {
+ return ERROR_MALFORMED;
+ }
+ return OK;
+}
+
AMRExtractor::AMRExtractor(const sp<DataSource> &source)
: mDataSource(source),
- mInitCheck(NO_INIT) {
+ mInitCheck(NO_INIT),
+ mOffsetTableLength(0) {
String8 mimeType;
float confidence;
if (!SniffAMR(mDataSource, &mimeType, &confidence, NULL)) {
@@ -101,25 +133,29 @@ AMRExtractor::AMRExtractor(const sp<DataSource> &source)
mMeta->setInt32(kKeyChannelCount, 1);
mMeta->setInt32(kKeySampleRate, mIsWide ? 16000 : 8000);
- size_t offset = mIsWide ? 9 : 6;
- uint8_t header;
- if (mDataSource->readAt(offset, &header, 1) != 1) {
- return;
- }
-
- unsigned FT = (header >> 3) & 0x0f;
-
- if (FT > 8 || (!mIsWide && FT > 7)) {
- return;
- }
-
- mFrameSize = getFrameSize(mIsWide, FT);
-
+ off64_t offset = mIsWide ? 9 : 6;
off64_t streamSize;
- if (mDataSource->getSize(&streamSize) == OK) {
- off64_t numFrames = streamSize / mFrameSize;
+ size_t frameSize, numFrames = 0;
+ int64_t duration = 0;
- mMeta->setInt64(kKeyDuration, 20000ll * numFrames);
+ if (mDataSource->getSize(&streamSize) == OK) {
+ while (offset < streamSize) {
+ if (getFrameSizeByOffset(source, offset, mIsWide, &frameSize) != OK) {
+ return;
+ }
+
+ if ((numFrames % 50 == 0) && (numFrames / 50 < OFFSET_TABLE_LEN)) {
+ CHECK_EQ(mOffsetTableLength, numFrames / 50);
+ mOffsetTable[mOffsetTableLength] = offset - (mIsWide ? 9: 6);
+ mOffsetTableLength ++;
+ }
+
+ offset += frameSize;
+ duration += 20000; // Each frame is 20ms
+ numFrames ++;
+ }
+
+ mMeta->setInt64(kKeyDuration, duration);
}
mInitCheck = OK;
@@ -149,7 +185,8 @@ sp<MediaSource> AMRExtractor::getTrack(size_t index) {
return NULL;
}
- return new AMRSource(mDataSource, mMeta, mFrameSize, mIsWide);
+ return new AMRSource(mDataSource, mMeta, mIsWide,
+ mOffsetTable, mOffsetTableLength);
}
sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) {
@@ -164,15 +201,18 @@ sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) {
AMRSource::AMRSource(
const sp<DataSource> &source, const sp<MetaData> &meta,
- size_t frameSize, bool isWide)
+ bool isWide, const off64_t *offset_table, size_t offset_table_length)
: mDataSource(source),
mMeta(meta),
- mFrameSize(frameSize),
mIsWide(isWide),
mOffset(mIsWide ? 9 : 6),
mCurrentTimeUs(0),
mStarted(false),
- mGroup(NULL) {
+ mGroup(NULL),
+ mOffsetTableLength(offset_table_length) {
+ if (mOffsetTableLength > 0 && mOffsetTableLength <= OFFSET_TABLE_LEN) {
+ memcpy ((char*)mOffsetTable, (char*)offset_table, sizeof(off64_t) * mOffsetTableLength);
+ }
}
AMRSource::~AMRSource() {
@@ -214,9 +254,25 @@ status_t AMRSource::read(
int64_t seekTimeUs;
ReadOptions::SeekMode mode;
if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+ size_t size;
int64_t seekFrame = seekTimeUs / 20000ll; // 20ms per frame.
mCurrentTimeUs = seekFrame * 20000ll;
- mOffset = seekFrame * mFrameSize + (mIsWide ? 9 : 6);
+
+ int index = seekFrame / 50;
+ if (index >= mOffsetTableLength) {
+ index = mOffsetTableLength - 1;
+ }
+
+ mOffset = mOffsetTable[index] + (mIsWide ? 9 : 6);
+
+ for (int i = 0; i< seekFrame - index * 50; i++) {
+ status_t err;
+ if ((err = getFrameSizeByOffset(mDataSource, mOffset,
+ mIsWide, &size)) != OK) {
+ return err;
+ }
+ mOffset += size;
+ }
}
uint8_t header;
@@ -236,16 +292,11 @@ status_t AMRSource::read(
unsigned FT = (header >> 3) & 0x0f;
- if (FT > 8 || (!mIsWide && FT > 7)) {
-
- LOGE("illegal AMR frame type %d", FT);
-
+ size_t frameSize = getFrameSize(mIsWide, FT);
+ if (frameSize == 0) {
return ERROR_MALFORMED;
}
- size_t frameSize = getFrameSize(mIsWide, FT);
- CHECK_EQ(frameSize, mFrameSize);
-
MediaBuffer *buffer;
status_t err = mGroup->acquire_buffer(&buffer);
if (err != OK) {
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 2d486e3..029b238 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \
DRMExtractor.cpp \
ESDS.cpp \
FileSource.cpp \
+ FLACExtractor.cpp \
HTTPStream.cpp \
JPEGSource.cpp \
MP3Extractor.cpp \
@@ -54,6 +55,7 @@ LOCAL_SRC_FILES:= \
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
$(TOP)/frameworks/base/include/media/stagefright/openmax \
+ $(TOP)/external/flac/include \
$(TOP)/external/tremolo \
$(TOP)/frameworks/base/media/libstagefright/rtsp
@@ -93,6 +95,7 @@ LOCAL_STATIC_LIBRARIES := \
libstagefright_rtsp \
libstagefright_id3 \
libstagefright_g711dec \
+ libFLAC \
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 89b3dab..11ac56c 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -378,11 +378,14 @@ status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
}
void AwesomePlayer::reset() {
+ LOGI("reset");
+
Mutex::Autolock autoLock(mLock);
reset_l();
}
void AwesomePlayer::reset_l() {
+ LOGI("reset_l");
mDisplayWidth = 0;
mDisplayHeight = 0;
@@ -408,6 +411,10 @@ void AwesomePlayer::reset_l() {
}
}
+ if (mFlags & PREPARING) {
+ LOGI("waiting until preparation is completes.");
+ }
+
while (mFlags & PREPARING) {
mPreparedCondition.wait(mLock);
}
@@ -431,6 +438,8 @@ void AwesomePlayer::reset_l() {
}
mAudioSource.clear();
+ LOGI("audio source cleared");
+
mTimeSource = NULL;
delete mAudioPlayer;
@@ -471,6 +480,8 @@ void AwesomePlayer::reset_l() {
IPCThreadState::self()->flushCommands();
}
+ LOGI("video source cleared");
+
mDurationUs = -1;
mFlags = 0;
mExtractorFlags = 0;
@@ -487,6 +498,8 @@ void AwesomePlayer::reset_l() {
mFileSource.clear();
mBitrate = -1;
+
+ LOGI("reset_l completed");
}
void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index ee0d792..e06fa81 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -23,6 +23,7 @@
#include "include/NuCachedSource2.h"
#include "include/NuHTTPDataSource.h"
#include "include/DRMExtractor.h"
+#include "include/FLACExtractor.h"
#include "matroska/MatroskaExtractor.h"
@@ -104,6 +105,7 @@ void DataSource::RegisterDefaultSniffers() {
RegisterSniffer(SniffMatroska);
RegisterSniffer(SniffOgg);
RegisterSniffer(SniffWAV);
+ RegisterSniffer(SniffFLAC);
RegisterSniffer(SniffAMR);
RegisterSniffer(SniffMPEG2TS);
RegisterSniffer(SniffMP3);
diff --git a/media/libstagefright/FLACExtractor.cpp b/media/libstagefright/FLACExtractor.cpp
new file mode 100644
index 0000000..8ba5a2d
--- /dev/null
+++ b/media/libstagefright/FLACExtractor.cpp
@@ -0,0 +1,813 @@
+/*
+ * 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_NDEBUG 0
+#define LOG_TAG "FLACExtractor"
+#include <utils/Log.h>
+
+#include "include/FLACExtractor.h"
+// Vorbis comments
+#include "include/OggExtractor.h"
+// libFLAC parser
+#include "FLAC/stream_decoder.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MediaBuffer.h>
+
+namespace android {
+
+class FLACParser;
+
+class FLACSource : public MediaSource {
+
+public:
+ FLACSource(
+ const sp<DataSource> &dataSource,
+ const sp<MetaData> &trackMetadata);
+
+ virtual status_t start(MetaData *params);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+protected:
+ virtual ~FLACSource();
+
+private:
+ sp<DataSource> mDataSource;
+ sp<MetaData> mTrackMetadata;
+ sp<FLACParser> mParser;
+ bool mInitCheck;
+ bool mStarted;
+
+ status_t init();
+
+ // no copy constructor or assignment
+ FLACSource(const FLACSource &);
+ FLACSource &operator=(const FLACSource &);
+
+};
+
+// FLACParser wraps a C libFLAC parser aka stream decoder
+
+class FLACParser : public RefBase {
+
+public:
+ FLACParser(
+ const sp<DataSource> &dataSource,
+ // If metadata pointers aren't provided, we don't fill them
+ const sp<MetaData> &fileMetadata = 0,
+ const sp<MetaData> &trackMetadata = 0);
+
+ status_t initCheck() const {
+ return mInitCheck;
+ }
+
+ // stream properties
+ unsigned getMaxBlockSize() const {
+ return mStreamInfo.max_blocksize;
+ }
+ unsigned getSampleRate() const {
+ return mStreamInfo.sample_rate;
+ }
+ unsigned getChannels() const {
+ return mStreamInfo.channels;
+ }
+ unsigned getBitsPerSample() const {
+ return mStreamInfo.bits_per_sample;
+ }
+ FLAC__uint64 getTotalSamples() const {
+ return mStreamInfo.total_samples;
+ }
+
+ // media buffers
+ void allocateBuffers();
+ void releaseBuffers();
+ MediaBuffer *readBuffer() {
+ return readBuffer(false, 0LL);
+ }
+ MediaBuffer *readBuffer(FLAC__uint64 sample) {
+ return readBuffer(true, sample);
+ }
+
+protected:
+ virtual ~FLACParser();
+
+private:
+ sp<DataSource> mDataSource;
+ sp<MetaData> mFileMetadata;
+ sp<MetaData> mTrackMetadata;
+ bool mInitCheck;
+
+ // media buffers
+ size_t mMaxBufferSize;
+ MediaBufferGroup *mGroup;
+ void (*mCopy)(short *dst, const int *const *src, unsigned nSamples);
+
+ // handle to underlying libFLAC parser
+ FLAC__StreamDecoder *mDecoder;
+
+ // current position within the data source
+ off64_t mCurrentPos;
+ bool mEOF;
+
+ // cached when the STREAMINFO metadata is parsed by libFLAC
+ FLAC__StreamMetadata_StreamInfo mStreamInfo;
+ bool mStreamInfoValid;
+
+ // cached when a decoded PCM block is "written" by libFLAC parser
+ bool mWriteRequested;
+ bool mWriteCompleted;
+ FLAC__FrameHeader mWriteHeader;
+ const FLAC__int32 * const *mWriteBuffer;
+
+ // most recent error reported by libFLAC parser
+ FLAC__StreamDecoderErrorStatus mErrorStatus;
+
+ status_t init();
+ MediaBuffer *readBuffer(bool doSeek, FLAC__uint64 sample);
+
+ // no copy constructor or assignment
+ FLACParser(const FLACParser &);
+ FLACParser &operator=(const FLACParser &);
+
+ // FLAC parser callbacks as C++ instance methods
+ FLAC__StreamDecoderReadStatus readCallback(
+ FLAC__byte buffer[], size_t *bytes);
+ FLAC__StreamDecoderSeekStatus seekCallback(
+ FLAC__uint64 absolute_byte_offset);
+ FLAC__StreamDecoderTellStatus tellCallback(
+ FLAC__uint64 *absolute_byte_offset);
+ FLAC__StreamDecoderLengthStatus lengthCallback(
+ FLAC__uint64 *stream_length);
+ FLAC__bool eofCallback();
+ FLAC__StreamDecoderWriteStatus writeCallback(
+ const FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
+ void metadataCallback(const FLAC__StreamMetadata *metadata);
+ void errorCallback(FLAC__StreamDecoderErrorStatus status);
+
+ // FLAC parser callbacks as C-callable functions
+ static FLAC__StreamDecoderReadStatus read_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__byte buffer[], size_t *bytes,
+ void *client_data);
+ static FLAC__StreamDecoderSeekStatus seek_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 absolute_byte_offset,
+ void *client_data);
+ static FLAC__StreamDecoderTellStatus tell_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *absolute_byte_offset,
+ void *client_data);
+ static FLAC__StreamDecoderLengthStatus length_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *stream_length,
+ void *client_data);
+ static FLAC__bool eof_callback(
+ const FLAC__StreamDecoder *decoder,
+ void *client_data);
+ static FLAC__StreamDecoderWriteStatus write_callback(
+ const FLAC__StreamDecoder *decoder,
+ const FLAC__Frame *frame, const FLAC__int32 * const buffer[],
+ void *client_data);
+ static void metadata_callback(
+ const FLAC__StreamDecoder *decoder,
+ const FLAC__StreamMetadata *metadata,
+ void *client_data);
+ static void error_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status,
+ void *client_data);
+
+};
+
+// The FLAC parser calls our C++ static callbacks using C calling conventions,
+// inside FLAC__stream_decoder_process_until_end_of_metadata
+// and FLAC__stream_decoder_process_single.
+// We immediately then call our corresponding C++ instance methods
+// with the same parameter list, but discard redundant information.
+
+FLAC__StreamDecoderReadStatus FLACParser::read_callback(
+ const FLAC__StreamDecoder *decoder, FLAC__byte buffer[],
+ size_t *bytes, void *client_data)
+{
+ return ((FLACParser *) client_data)->readCallback(buffer, bytes);
+}
+
+FLAC__StreamDecoderSeekStatus FLACParser::seek_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 absolute_byte_offset, void *client_data)
+{
+ return ((FLACParser *) client_data)->seekCallback(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderTellStatus FLACParser::tell_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data)
+{
+ return ((FLACParser *) client_data)->tellCallback(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderLengthStatus FLACParser::length_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *stream_length, void *client_data)
+{
+ return ((FLACParser *) client_data)->lengthCallback(stream_length);
+}
+
+FLAC__bool FLACParser::eof_callback(
+ const FLAC__StreamDecoder *decoder, void *client_data)
+{
+ return ((FLACParser *) client_data)->eofCallback();
+}
+
+FLAC__StreamDecoderWriteStatus FLACParser::write_callback(
+ const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
+ const FLAC__int32 * const buffer[], void *client_data)
+{
+ return ((FLACParser *) client_data)->writeCallback(frame, buffer);
+}
+
+void FLACParser::metadata_callback(
+ const FLAC__StreamDecoder *decoder,
+ const FLAC__StreamMetadata *metadata, void *client_data)
+{
+ ((FLACParser *) client_data)->metadataCallback(metadata);
+}
+
+void FLACParser::error_callback(
+ const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status, void *client_data)
+{
+ ((FLACParser *) client_data)->errorCallback(status);
+}
+
+// These are the corresponding callbacks with C++ calling conventions
+
+FLAC__StreamDecoderReadStatus FLACParser::readCallback(
+ FLAC__byte buffer[], size_t *bytes)
+{
+ size_t requested = *bytes;
+ ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested);
+ if (0 > actual) {
+ *bytes = 0;
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ } else if (0 == actual) {
+ *bytes = 0;
+ mEOF = true;
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ } else {
+ assert(actual <= requested);
+ *bytes = actual;
+ mCurrentPos += actual;
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+ }
+}
+
+FLAC__StreamDecoderSeekStatus FLACParser::seekCallback(
+ FLAC__uint64 absolute_byte_offset)
+{
+ mCurrentPos = absolute_byte_offset;
+ mEOF = false;
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+FLAC__StreamDecoderTellStatus FLACParser::tellCallback(
+ FLAC__uint64 *absolute_byte_offset)
+{
+ *absolute_byte_offset = mCurrentPos;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback(
+ FLAC__uint64 *stream_length)
+{
+ off64_t size;
+ if (OK == mDataSource->getSize(&size)) {
+ *stream_length = size;
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+ } else {
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
+ }
+}
+
+FLAC__bool FLACParser::eofCallback()
+{
+ return mEOF;
+}
+
+FLAC__StreamDecoderWriteStatus FLACParser::writeCallback(
+ const FLAC__Frame *frame, const FLAC__int32 * const buffer[])
+{
+ if (mWriteRequested) {
+ mWriteRequested = false;
+ // FLAC parser doesn't free or realloc buffer until next frame or finish
+ mWriteHeader = frame->header;
+ mWriteBuffer = buffer;
+ mWriteCompleted = true;
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ } else {
+ LOGE("FLACParser::writeCallback unexpected");
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+ }
+}
+
+void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata)
+{
+ switch (metadata->type) {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ if (!mStreamInfoValid) {
+ mStreamInfo = metadata->data.stream_info;
+ mStreamInfoValid = true;
+ } else {
+ LOGE("FLACParser::metadataCallback unexpected STREAMINFO");
+ }
+ break;
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ {
+ const FLAC__StreamMetadata_VorbisComment *vc;
+ vc = &metadata->data.vorbis_comment;
+ for (FLAC__uint32 i = 0; i < vc->num_comments; ++i) {
+ FLAC__StreamMetadata_VorbisComment_Entry *vce;
+ vce = &vc->comments[i];
+ if (mFileMetadata != 0) {
+ parseVorbisComment(mFileMetadata, (const char *) vce->entry,
+ vce->length);
+ }
+ }
+ }
+ break;
+ case FLAC__METADATA_TYPE_PICTURE:
+ if (mFileMetadata != 0) {
+ const FLAC__StreamMetadata_Picture *p = &metadata->data.picture;
+ mFileMetadata->setData(kKeyAlbumArt,
+ MetaData::TYPE_NONE, p->data, p->data_length);
+ mFileMetadata->setCString(kKeyAlbumArtMIME, p->mime_type);
+ }
+ break;
+ default:
+ LOGW("FLACParser::metadataCallback unexpected type %u", metadata->type);
+ break;
+ }
+}
+
+void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status)
+{
+ LOGE("FLACParser::errorCallback status=%d", status);
+ mErrorStatus = status;
+}
+
+// Copy samples from FLAC native 32-bit non-interleaved to 16-bit interleaved.
+// These are candidates for optimization if needed.
+
+static void copyMono8(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i] << 8;
+ }
+}
+
+static void copyStereo8(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i] << 8;
+ *dst++ = src[1][i] << 8;
+ }
+}
+
+static void copyMono16(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i];
+ }
+}
+
+static void copyStereo16(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i];
+ *dst++ = src[1][i];
+ }
+}
+
+// 24-bit versions should do dithering or noise-shaping, here or in AudioFlinger
+
+static void copyMono24(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i] >> 8;
+ }
+}
+
+static void copyStereo24(short *dst, const int *const *src, unsigned nSamples)
+{
+ for (unsigned i = 0; i < nSamples; ++i) {
+ *dst++ = src[0][i] >> 8;
+ *dst++ = src[1][i] >> 8;
+ }
+}
+
+static void copyTrespass(short *dst, const int *const *src, unsigned nSamples)
+{
+ TRESPASS();
+}
+
+// FLACParser
+
+FLACParser::FLACParser(
+ const sp<DataSource> &dataSource,
+ const sp<MetaData> &fileMetadata,
+ const sp<MetaData> &trackMetadata)
+ : mDataSource(dataSource),
+ mFileMetadata(fileMetadata),
+ mTrackMetadata(trackMetadata),
+ mInitCheck(false),
+ mMaxBufferSize(0),
+ mGroup(NULL),
+ mCopy(copyTrespass),
+ mDecoder(NULL),
+ mCurrentPos(0LL),
+ mEOF(false),
+ mStreamInfoValid(false),
+ mWriteRequested(false),
+ mWriteCompleted(false),
+ mWriteBuffer(NULL),
+ mErrorStatus((FLAC__StreamDecoderErrorStatus) -1)
+{
+ LOGV("FLACParser::FLACParser");
+ memset(&mStreamInfo, 0, sizeof(mStreamInfo));
+ memset(&mWriteHeader, 0, sizeof(mWriteHeader));
+ mInitCheck = init();
+}
+
+FLACParser::~FLACParser()
+{
+ LOGV("FLACParser::~FLACParser");
+ if (mDecoder != NULL) {
+ FLAC__stream_decoder_delete(mDecoder);
+ mDecoder = NULL;
+ }
+}
+
+status_t FLACParser::init()
+{
+ // setup libFLAC parser
+ mDecoder = FLAC__stream_decoder_new();
+ if (mDecoder == NULL) {
+ // The new should succeed, since probably all it does is a malloc
+ // that always succeeds in Android. But to avoid dependence on the
+ // libFLAC internals, we check and log here.
+ LOGE("new failed");
+ return NO_INIT;
+ }
+ FLAC__stream_decoder_set_md5_checking(mDecoder, false);
+ FLAC__stream_decoder_set_metadata_ignore_all(mDecoder);
+ FLAC__stream_decoder_set_metadata_respond(
+ mDecoder, FLAC__METADATA_TYPE_STREAMINFO);
+ FLAC__stream_decoder_set_metadata_respond(
+ mDecoder, FLAC__METADATA_TYPE_PICTURE);
+ FLAC__stream_decoder_set_metadata_respond(
+ mDecoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
+ FLAC__StreamDecoderInitStatus initStatus;
+ initStatus = FLAC__stream_decoder_init_stream(
+ mDecoder,
+ read_callback, seek_callback, tell_callback,
+ length_callback, eof_callback, write_callback,
+ metadata_callback, error_callback, (void *) this);
+ if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ // A failure here probably indicates a programming error and so is
+ // unlikely to happen. But we check and log here similarly to above.
+ LOGE("init_stream failed %d", initStatus);
+ return NO_INIT;
+ }
+ // parse all metadata
+ if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) {
+ LOGE("end_of_metadata failed");
+ return NO_INIT;
+ }
+ if (mStreamInfoValid) {
+ // check channel count
+ switch (getChannels()) {
+ case 1:
+ case 2:
+ break;
+ default:
+ LOGE("unsupported channel count %u", getChannels());
+ return NO_INIT;
+ }
+ // check bit depth
+ switch (getBitsPerSample()) {
+ case 8:
+ case 16:
+ case 24:
+ break;
+ default:
+ LOGE("unsupported bits per sample %u", getBitsPerSample());
+ return NO_INIT;
+ }
+ // check sample rate
+ switch (getSampleRate()) {
+ case 8000:
+ case 11025:
+ case 12000:
+ case 16000:
+ case 22050:
+ case 24000:
+ case 32000:
+ case 44100:
+ case 48000:
+ break;
+ default:
+ // 96000 would require a proper downsampler in AudioFlinger
+ LOGE("unsupported sample rate %u", getSampleRate());
+ return NO_INIT;
+ }
+ // configure the appropriate copy function, defaulting to trespass
+ static const struct {
+ unsigned mChannels;
+ unsigned mBitsPerSample;
+ void (*mCopy)(short *dst, const int *const *src, unsigned nSamples);
+ } table[] = {
+ { 1, 8, copyMono8 },
+ { 2, 8, copyStereo8 },
+ { 1, 16, copyMono16 },
+ { 2, 16, copyStereo16 },
+ { 1, 24, copyMono24 },
+ { 2, 24, copyStereo24 },
+ };
+ for (unsigned i = 0; i < sizeof(table)/sizeof(table[0]); ++i) {
+ if (table[i].mChannels == getChannels() &&
+ table[i].mBitsPerSample == getBitsPerSample()) {
+ mCopy = table[i].mCopy;
+ break;
+ }
+ }
+ // populate track metadata
+ if (mTrackMetadata != 0) {
+ mTrackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+ mTrackMetadata->setInt32(kKeyChannelCount, getChannels());
+ mTrackMetadata->setInt32(kKeySampleRate, getSampleRate());
+ // sample rate is non-zero, so division by zero not possible
+ mTrackMetadata->setInt64(kKeyDuration,
+ (getTotalSamples() * 1000000LL) / getSampleRate());
+ }
+ } else {
+ LOGE("missing STREAMINFO");
+ return NO_INIT;
+ }
+ if (mFileMetadata != 0) {
+ mFileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FLAC);
+ }
+ return OK;
+}
+
+void FLACParser::allocateBuffers()
+{
+ CHECK(mGroup == NULL);
+ mGroup = new MediaBufferGroup;
+ mMaxBufferSize = getMaxBlockSize() * getChannels() * sizeof(short);
+ mGroup->add_buffer(new MediaBuffer(mMaxBufferSize));
+}
+
+void FLACParser::releaseBuffers()
+{
+ CHECK(mGroup != NULL);
+ delete mGroup;
+ mGroup = NULL;
+}
+
+MediaBuffer *FLACParser::readBuffer(bool doSeek, FLAC__uint64 sample)
+{
+ mWriteRequested = true;
+ mWriteCompleted = false;
+ if (doSeek) {
+ // We implement the seek callback, so this works without explicit flush
+ if (!FLAC__stream_decoder_seek_absolute(mDecoder, sample)) {
+ LOGE("FLACParser::readBuffer seek to sample %llu failed", sample);
+ return NULL;
+ }
+ LOGV("FLACParser::readBuffer seek to sample %llu succeeded", sample);
+ } else {
+ if (!FLAC__stream_decoder_process_single(mDecoder)) {
+ LOGE("FLACParser::readBuffer process_single failed");
+ return NULL;
+ }
+ }
+ if (!mWriteCompleted) {
+ LOGV("FLACParser::readBuffer write did not complete");
+ return NULL;
+ }
+ // verify that block header keeps the promises made by STREAMINFO
+ unsigned blocksize = mWriteHeader.blocksize;
+ if (blocksize == 0 || blocksize > getMaxBlockSize()) {
+ LOGE("FLACParser::readBuffer write invalid blocksize %u", blocksize);
+ return NULL;
+ }
+ if (mWriteHeader.sample_rate != getSampleRate() ||
+ mWriteHeader.channels != getChannels() ||
+ mWriteHeader.bits_per_sample != getBitsPerSample()) {
+ LOGE("FLACParser::readBuffer write changed parameters mid-stream");
+ }
+ // acquire a media buffer
+ CHECK(mGroup != NULL);
+ MediaBuffer *buffer;
+ status_t err = mGroup->acquire_buffer(&buffer);
+ if (err != OK) {
+ return NULL;
+ }
+ size_t bufferSize = blocksize * getChannels() * sizeof(short);
+ CHECK(bufferSize <= mMaxBufferSize);
+ short *data = (short *) buffer->data();
+ buffer->set_range(0, bufferSize);
+ // copy PCM from FLAC write buffer to our media buffer, with interleaving
+ (*mCopy)(data, mWriteBuffer, blocksize);
+ // fill in buffer metadata
+ CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER);
+ FLAC__uint64 sampleNumber = mWriteHeader.number.sample_number;
+ int64_t timeUs = (1000000LL * sampleNumber) / getSampleRate();
+ buffer->meta_data()->setInt64(kKeyTime, timeUs);
+ buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ return buffer;
+}
+
+// FLACsource
+
+FLACSource::FLACSource(
+ const sp<DataSource> &dataSource,
+ const sp<MetaData> &trackMetadata)
+ : mDataSource(dataSource),
+ mTrackMetadata(trackMetadata),
+ mParser(0),
+ mInitCheck(false),
+ mStarted(false)
+{
+ LOGV("FLACSource::FLACSource");
+ mInitCheck = init();
+}
+
+FLACSource::~FLACSource()
+{
+ LOGV("~FLACSource::FLACSource");
+ if (mStarted) {
+ stop();
+ }
+}
+
+status_t FLACSource::start(MetaData *params)
+{
+ LOGV("FLACSource::start");
+
+ CHECK(!mStarted);
+ mParser->allocateBuffers();
+ mStarted = true;
+
+ return OK;
+}
+
+status_t FLACSource::stop()
+{
+ LOGV("FLACSource::stop");
+
+ CHECK(mStarted);
+ mParser->releaseBuffers();
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> FLACSource::getFormat()
+{
+ return mTrackMetadata;
+}
+
+status_t FLACSource::read(
+ MediaBuffer **outBuffer, const ReadOptions *options)
+{
+ MediaBuffer *buffer;
+ // process an optional seek request
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode mode;
+ if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) {
+ FLAC__uint64 sample;
+ if (seekTimeUs <= 0LL) {
+ sample = 0LL;
+ } else {
+ // sample and total samples are both zero-based, and seek to EOF ok
+ sample = (seekTimeUs * mParser->getSampleRate()) / 1000000LL;
+ if (sample >= mParser->getTotalSamples()) {
+ sample = mParser->getTotalSamples();
+ }
+ }
+ buffer = mParser->readBuffer(sample);
+ // otherwise read sequentially
+ } else {
+ buffer = mParser->readBuffer();
+ }
+ *outBuffer = buffer;
+ return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM;
+}
+
+status_t FLACSource::init()
+{
+ LOGV("FLACSource::init");
+ // re-use the same track metadata passed into constructor from FLACExtractor
+ mParser = new FLACParser(mDataSource);
+ return mParser->initCheck();
+}
+
+// FLACExtractor
+
+FLACExtractor::FLACExtractor(
+ const sp<DataSource> &dataSource)
+ : mDataSource(dataSource),
+ mInitCheck(false)
+{
+ LOGV("FLACExtractor::FLACExtractor");
+ mInitCheck = init();
+}
+
+FLACExtractor::~FLACExtractor()
+{
+ LOGV("~FLACExtractor::FLACExtractor");
+}
+
+size_t FLACExtractor::countTracks()
+{
+ return mInitCheck == OK ? 1 : 0;
+}
+
+sp<MediaSource> FLACExtractor::getTrack(size_t index)
+{
+ if (mInitCheck != OK || index > 0) {
+ return NULL;
+ }
+ return new FLACSource(mDataSource, mTrackMetadata);
+}
+
+sp<MetaData> FLACExtractor::getTrackMetaData(
+ size_t index, uint32_t flags)
+{
+ if (mInitCheck != OK || index > 0) {
+ return NULL;
+ }
+ return mTrackMetadata;
+}
+
+status_t FLACExtractor::init()
+{
+ mFileMetadata = new MetaData;
+ mTrackMetadata = new MetaData;
+ // FLACParser will fill in the metadata for us
+ mParser = new FLACParser(mDataSource, mFileMetadata, mTrackMetadata);
+ return mParser->initCheck();
+}
+
+sp<MetaData> FLACExtractor::getMetaData()
+{
+ return mFileMetadata;
+}
+
+// Sniffer
+
+bool SniffFLAC(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *)
+{
+ // first 4 is the signature word
+ // second 4 is the sizeof STREAMINFO
+ // 042 is the mandatory STREAMINFO
+ // no need to read rest of the header, as a premature EOF will be caught later
+ uint8_t header[4+4];
+ if (source->readAt(0, header, sizeof(header)) != sizeof(header)
+ || memcmp("fLaC\0\0\0\042", header, 4+4))
+ {
+ return false;
+ }
+
+ *mimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
+ *confidence = 0.5;
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index e6e98aa..108a1d1 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -1074,6 +1074,20 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
break;
}
+ case FOURCC('c', 't', 't', 's'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setCompositionTimeToSampleParams(
+ data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
case FOURCC('s', 't', 's', 's'):
{
status_t err =
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 4599fca..b50af89 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -35,6 +35,7 @@ const char *MEDIA_MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw";
+const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index d12ac64..08ed206 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -26,6 +26,7 @@
#include "include/MPEG2TSExtractor.h"
#include "include/DRMExtractor.h"
#include "include/WVMExtractor.h"
+#include "include/FLACExtractor.h"
#include "matroska/MatroskaExtractor.h"
@@ -85,6 +86,8 @@ sp<MediaExtractor> MediaExtractor::Create(
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
return new AMRExtractor(source);
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
+ return new FLACExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
return new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 2a19b25..247ace7 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -3269,7 +3269,7 @@ status_t OMXCodec::start(MetaData *meta) {
}
status_t OMXCodec::stop() {
- CODEC_LOGV("stop mState=%d", mState);
+ CODEC_LOGI("stop mState=%d", mState);
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index cf622af..0e51caf 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -114,7 +114,6 @@ private:
MediaBuffer *buffer, uint8_t type);
void parseFileMetaData();
- void extractAlbumArt(const void *data, size_t size);
uint64_t findPrevGranulePosition(off64_t pageOffset);
@@ -122,6 +121,9 @@ private:
MyVorbisExtractor &operator=(const MyVorbisExtractor &);
};
+static void extractAlbumArt(
+ const sp<MetaData> &fileMeta, const void *data, size_t size);
+
////////////////////////////////////////////////////////////////////////////////
OggSource::OggSource(const sp<OggExtractor> &extractor)
@@ -654,6 +656,17 @@ void MyVorbisExtractor::parseFileMetaData() {
mFileMeta = new MetaData;
mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
+ for (int i = 0; i < mVc.comments; ++i) {
+ const char *comment = mVc.user_comments[i];
+ size_t commentLength = mVc.comment_lengths[i];
+ parseVorbisComment(mFileMeta, comment, commentLength);
+ //LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]);
+ }
+}
+
+void parseVorbisComment(
+ const sp<MetaData> &fileMeta, const char *comment, size_t commentLength)
+{
struct {
const char *const mTag;
uint32_t mKey;
@@ -675,33 +688,25 @@ void MyVorbisExtractor::parseFileMetaData() {
{ "ANDROID_LOOP", kKeyAutoLoop },
};
- for (int i = 0; i < mVc.comments; ++i) {
- const char *comment = mVc.user_comments[i];
-
for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
size_t tagLen = strlen(kMap[j].mTag);
if (!strncasecmp(kMap[j].mTag, comment, tagLen)
&& comment[tagLen] == '=') {
if (kMap[j].mKey == kKeyAlbumArt) {
extractAlbumArt(
+ fileMeta,
&comment[tagLen + 1],
- mVc.comment_lengths[i] - tagLen - 1);
+ commentLength - tagLen - 1);
} else if (kMap[j].mKey == kKeyAutoLoop) {
if (!strcasecmp(&comment[tagLen + 1], "true")) {
- mFileMeta->setInt32(kKeyAutoLoop, true);
+ fileMeta->setInt32(kKeyAutoLoop, true);
}
} else {
- mFileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
+ fileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
}
}
}
- }
-#if 0
- for (int i = 0; i < mVc.comments; ++i) {
- LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]);
- }
-#endif
}
// The returned buffer should be free()d.
@@ -769,7 +774,8 @@ static uint8_t *DecodeBase64(const char *s, size_t size, size_t *outSize) {
return (uint8_t *)buffer;
}
-void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) {
+static void extractAlbumArt(
+ const sp<MetaData> &fileMeta, const void *data, size_t size) {
LOGV("extractAlbumArt from '%s'", (const char *)data);
size_t flacSize;
@@ -833,10 +839,10 @@ void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) {
LOGV("got image data, %d trailing bytes",
flacSize - 32 - typeLen - descLen - dataLen);
- mFileMeta->setData(
+ fileMeta->setData(
kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen);
- mFileMeta->setCString(kKeyAlbumArtMIME, type);
+ fileMeta->setCString(kKeyAlbumArtMIME, type);
exit:
free(flac);
diff --git a/media/libstagefright/SampleIterator.cpp b/media/libstagefright/SampleIterator.cpp
index 062ab9b..c7b00b1 100644
--- a/media/libstagefright/SampleIterator.cpp
+++ b/media/libstagefright/SampleIterator.cpp
@@ -307,6 +307,8 @@ status_t SampleIterator::findSampleTime(
*time = mTTSSampleTime + mTTSDuration * (sampleIndex - mTTSSampleIndex);
+ *time += mTable->getCompositionTimeOffset(sampleIndex);
+
return OK;
}
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index a9163fc..423df70 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -53,6 +53,8 @@ SampleTable::SampleTable(const sp<DataSource> &source)
mNumSampleSizes(0),
mTimeToSampleCount(0),
mTimeToSample(NULL),
+ mCompositionTimeDeltaEntries(NULL),
+ mNumCompositionTimeDeltaEntries(0),
mSyncSampleOffset(-1),
mNumSyncSamples(0),
mSyncSamples(NULL),
@@ -68,6 +70,9 @@ SampleTable::~SampleTable() {
delete[] mSyncSamples;
mSyncSamples = NULL;
+ delete[] mCompositionTimeDeltaEntries;
+ mCompositionTimeDeltaEntries = NULL;
+
delete[] mTimeToSample;
mTimeToSample = NULL;
@@ -260,6 +265,51 @@ status_t SampleTable::setTimeToSampleParams(
return OK;
}
+status_t SampleTable::setCompositionTimeToSampleParams(
+ off64_t data_offset, size_t data_size) {
+ LOGI("There are reordered frames present.");
+
+ if (mCompositionTimeDeltaEntries != NULL || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header))
+ < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ size_t numEntries = U32_AT(&header[4]);
+
+ if (data_size != (numEntries + 1) * 8) {
+ return ERROR_MALFORMED;
+ }
+
+ mNumCompositionTimeDeltaEntries = numEntries;
+ mCompositionTimeDeltaEntries = new uint32_t[2 * numEntries];
+
+ if (mDataSource->readAt(
+ data_offset + 8, mCompositionTimeDeltaEntries, numEntries * 8)
+ < (ssize_t)numEntries * 8) {
+ delete[] mCompositionTimeDeltaEntries;
+ mCompositionTimeDeltaEntries = NULL;
+
+ return ERROR_IO;
+ }
+
+ for (size_t i = 0; i < 2 * numEntries; ++i) {
+ mCompositionTimeDeltaEntries[i] = ntohl(mCompositionTimeDeltaEntries[i]);
+ }
+
+ return OK;
+}
+
status_t SampleTable::setSyncSampleParams(off64_t data_offset, size_t data_size) {
if (mSyncSampleOffset >= 0 || data_size < 8) {
return ERROR_MALFORMED;
@@ -333,6 +383,8 @@ uint32_t abs_difference(uint32_t time1, uint32_t time2) {
status_t SampleTable::findSampleAtTime(
uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
+ // XXX this currently uses decoding time, instead of composition time.
+
*sample_index = 0;
Mutex::Autolock autoLock(mLock);
@@ -607,5 +659,26 @@ status_t SampleTable::getMetaDataForSample(
return OK;
}
+uint32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) const {
+ if (mCompositionTimeDeltaEntries == NULL) {
+ return 0;
+ }
+
+ uint32_t curSample = 0;
+ for (size_t i = 0; i < mNumCompositionTimeDeltaEntries; ++i) {
+ uint32_t sampleCount = mCompositionTimeDeltaEntries[2 * i];
+
+ if (sampleIndex < curSample + sampleCount) {
+ uint32_t sampleDelta = mCompositionTimeDeltaEntries[2 * i + 1];
+
+ return sampleDelta;
+ }
+
+ curSample += sampleCount;
+ }
+
+ return 0;
+}
+
} // namespace android
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index be3df7c..84f65ff 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -37,7 +37,7 @@ static bool FileHasAcceptableExtension(const char *extension) {
".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2",
".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota",
- ".mkv", ".mka", ".webm", ".ts", ".fl"
+ ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac"
};
static const size_t kNumValidExtensions =
sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
diff --git a/media/libstagefright/include/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h
index 1cdf36d..589d837 100644
--- a/media/libstagefright/include/AMRExtractor.h
+++ b/media/libstagefright/include/AMRExtractor.h
@@ -24,6 +24,7 @@ namespace android {
struct AMessage;
class String8;
+#define OFFSET_TABLE_LEN 300
class AMRExtractor : public MediaExtractor {
public:
@@ -42,9 +43,11 @@ private:
sp<DataSource> mDataSource;
sp<MetaData> mMeta;
status_t mInitCheck;
- size_t mFrameSize;
bool mIsWide;
+ off64_t mOffsetTable[OFFSET_TABLE_LEN]; //5 min
+ size_t mOffsetTableLength;
+
AMRExtractor(const AMRExtractor &);
AMRExtractor &operator=(const AMRExtractor &);
};
diff --git a/media/libstagefright/include/FLACExtractor.h b/media/libstagefright/include/FLACExtractor.h
new file mode 100644
index 0000000..ded91c2
--- /dev/null
+++ b/media/libstagefright/include/FLACExtractor.h
@@ -0,0 +1,64 @@
+/*
+ * 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 FLAC_EXTRACTOR_H_
+#define FLAC_EXTRACTOR_H_
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class FLACParser;
+
+class FLACExtractor : public MediaExtractor {
+
+public:
+ // Extractor assumes ownership of source
+ FLACExtractor(const sp<DataSource> &source);
+
+ virtual size_t countTracks();
+ virtual sp<MediaSource> getTrack(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+
+ virtual sp<MetaData> getMetaData();
+
+protected:
+ virtual ~FLACExtractor();
+
+private:
+ sp<DataSource> mDataSource;
+ sp<FLACParser> mParser;
+ status_t mInitCheck;
+ sp<MetaData> mFileMetadata;
+
+ // There is only one track
+ sp<MetaData> mTrackMetadata;
+
+ status_t init();
+
+ FLACExtractor(const FLACExtractor &);
+ FLACExtractor &operator=(const FLACExtractor &);
+
+};
+
+bool SniffFLAC(const sp<DataSource> &source, String8 *mimeType,
+ float *confidence, sp<AMessage> *);
+
+} // namespace android
+
+#endif // FLAC_EXTRACTOR_H_
diff --git a/media/libstagefright/include/OggExtractor.h b/media/libstagefright/include/OggExtractor.h
index 1eda025..a41f681 100644
--- a/media/libstagefright/include/OggExtractor.h
+++ b/media/libstagefright/include/OggExtractor.h
@@ -57,6 +57,9 @@ bool SniffOgg(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *);
+void parseVorbisComment(
+ const sp<MetaData> &fileMeta, const char *comment, size_t commentLength);
+
} // namespace android
#endif // OGG_EXTRACTOR_H_
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index c5e8136..2f95de9 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -46,6 +46,9 @@ public:
status_t setTimeToSampleParams(off64_t data_offset, size_t data_size);
+ status_t setCompositionTimeToSampleParams(
+ off64_t data_offset, size_t data_size);
+
status_t setSyncSampleParams(off64_t data_offset, size_t data_size);
////////////////////////////////////////////////////////////////////////////
@@ -104,6 +107,9 @@ private:
uint32_t mTimeToSampleCount;
uint32_t *mTimeToSample;
+ uint32_t *mCompositionTimeDeltaEntries;
+ size_t mNumCompositionTimeDeltaEntries;
+
off64_t mSyncSampleOffset;
uint32_t mNumSyncSamples;
uint32_t *mSyncSamples;
@@ -122,6 +128,8 @@ private:
status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size);
+ uint32_t getCompositionTimeOffset(uint32_t sampleIndex) const;
+
SampleTable(const SampleTable &);
SampleTable &operator=(const SampleTable &);
};
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk
index 70dc340..c25285e 100644
--- a/media/mtp/Android.mk
+++ b/media/mtp/Android.mk
@@ -21,7 +21,6 @@ ifneq ($(TARGET_SIMULATOR),true)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- MtpClient.cpp \
MtpDataPacket.cpp \
MtpDebug.cpp \
MtpDevice.cpp \
@@ -53,7 +52,6 @@ ifeq ($(HOST_OS),linux)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- MtpClient.cpp \
MtpDataPacket.cpp \
MtpDebug.cpp \
MtpDevice.cpp \
diff --git a/media/mtp/MtpClient.cpp b/media/mtp/MtpClient.cpp
deleted file mode 100644
index c830540..0000000
--- a/media/mtp/MtpClient.cpp
+++ /dev/null
@@ -1,251 +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_TAG "MtpClient"
-
-#include "MtpDebug.h"
-#include "MtpClient.h"
-#include "MtpDevice.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#include <usbhost/usbhost.h>
-
-struct usb_device;
-
-namespace android {
-
-static bool isMtpDevice(uint16_t vendor, uint16_t product) {
- // Sandisk Sansa Fuze
- if (vendor == 0x0781 && product == 0x74c2)
- return true;
- // Samsung YP-Z5
- if (vendor == 0x04e8 && product == 0x503c)
- return true;
- return false;
-}
-
-class MtpClientThread : public Thread {
-private:
- MtpClient* mClient;
-
-public:
- MtpClientThread(MtpClient* client)
- : mClient(client)
- {
- }
-
- virtual bool threadLoop() {
- return mClient->threadLoop();
- }
-};
-
-
-MtpClient::MtpClient()
- : mThread(NULL),
- mUsbHostContext(NULL),
- mDone(false)
-{
-}
-
-MtpClient::~MtpClient() {
- usb_host_cleanup(mUsbHostContext);
-}
-
-bool MtpClient::start() {
- Mutex::Autolock autoLock(mMutex);
-
- if (mThread)
- return true;
-
- mUsbHostContext = usb_host_init();
- if (!mUsbHostContext)
- return false;
-
- mThread = new MtpClientThread(this);
- mThread->run("MtpClientThread");
- // wait for the thread to do initial device discovery before returning
- mThreadStartCondition.wait(mMutex);
-
- return true;
-}
-
-void MtpClient::stop() {
- mDone = true;
-}
-
-MtpDevice* MtpClient::getDevice(int id) {
- for (int i = 0; i < mDeviceList.size(); i++) {
- MtpDevice* device = mDeviceList[i];
- if (device->getID() == id)
- return device;
- }
- return NULL;
-}
-
-bool MtpClient::usbDeviceAdded(const char *devname) {
- struct usb_descriptor_header* desc;
- struct usb_descriptor_iter iter;
-
- struct usb_device *device = usb_device_open(devname);
- if (!device) {
- LOGE("usb_device_open failed\n");
- return mDone;
- }
-
- usb_descriptor_iter_init(device, &iter);
-
- while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
- if (desc->bDescriptorType == USB_DT_INTERFACE) {
- struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
-
- if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
- interface->bInterfaceSubClass == 1 && // Still Image Capture
- interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470)
- {
- LOGD("Found camera: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
- usb_device_get_product_name(device));
- } else if (interface->bInterfaceClass == 0xFF &&
- interface->bInterfaceSubClass == 0xFF &&
- interface->bInterfaceProtocol == 0) {
- char* interfaceName = usb_device_get_string(device, interface->iInterface);
- if (!interfaceName || strcmp(interfaceName, "MTP"))
- continue;
- // Looks like an android style MTP device
- LOGD("Found MTP device: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
- usb_device_get_product_name(device));
- } else {
- // look for special cased devices based on vendor/product ID
- // we are doing this mainly for testing purposes
- uint16_t vendor = usb_device_get_vendor_id(device);
- uint16_t product = usb_device_get_product_id(device);
- if (!isMtpDevice(vendor, product)) {
- // not an MTP or PTP device
- continue;
- }
- // request MTP OS string and descriptor
- // some music players need to see this before entering MTP mode.
- char buffer[256];
- memset(buffer, 0, sizeof(buffer));
- int ret = usb_device_send_control(device,
- USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD,
- USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE,
- 0, sizeof(buffer), buffer);
- printf("usb_device_send_control returned %d errno: %d\n", ret, errno);
- if (ret > 0) {
- printf("got MTP string %s\n", buffer);
- ret = usb_device_send_control(device,
- USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1,
- 0, 4, sizeof(buffer), buffer);
- printf("OS descriptor got %d\n", ret);
- } else {
- printf("no MTP string\n");
- }
- }
-
- // if we got here, then we have a likely MTP or PTP device
-
- // interface should be followed by three endpoints
- struct usb_endpoint_descriptor *ep;
- struct usb_endpoint_descriptor *ep_in_desc = NULL;
- struct usb_endpoint_descriptor *ep_out_desc = NULL;
- struct usb_endpoint_descriptor *ep_intr_desc = NULL;
- for (int i = 0; i < 3; i++) {
- ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
- if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
- LOGE("endpoints not found\n");
- return mDone;
- }
- if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
- if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
- ep_in_desc = ep;
- else
- ep_out_desc = ep;
- } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
- ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
- ep_intr_desc = ep;
- }
- }
- if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
- LOGE("endpoints not found\n");
- return mDone;
- }
-
- if (usb_device_claim_interface(device, interface->bInterfaceNumber)) {
- LOGE("usb_device_claim_interface failed errno: %d\n", errno);
- return mDone;
- }
-
- MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber,
- ep_in_desc, ep_out_desc, ep_intr_desc);
- mDeviceList.add(mtpDevice);
- mtpDevice->initialize();
- deviceAdded(mtpDevice);
- return mDone;
- }
- }
-
- usb_device_close(device);
- return mDone;
-}
-
-bool MtpClient::usbDeviceRemoved(const char *devname) {
- for (int i = 0; i < mDeviceList.size(); i++) {
- MtpDevice* device = mDeviceList[i];
- if (!strcmp(devname, device->getDeviceName())) {
- deviceRemoved(device);
- mDeviceList.removeAt(i);
- delete device;
- LOGD("Camera removed!\n");
- break;
- }
- }
- return mDone;
-}
-
-bool MtpClient::usbDiscoveryDone() {
- Mutex::Autolock autoLock(mMutex);
- mThreadStartCondition.signal();
- return mDone;
-}
-
-bool MtpClient::threadLoop() {
- usb_host_run(mUsbHostContext, usb_device_added, usb_device_removed, usb_discovery_done, this);
- return false;
-}
-
-int MtpClient::usb_device_added(const char *devname, void* client_data) {
- LOGD("usb_device_added %s\n", devname);
- return ((MtpClient *)client_data)->usbDeviceAdded(devname);
-}
-
-int MtpClient::usb_device_removed(const char *devname, void* client_data) {
- LOGD("usb_device_removed %s\n", devname);
- return ((MtpClient *)client_data)->usbDeviceRemoved(devname);
-}
-
-int MtpClient::usb_discovery_done(void* client_data) {
- LOGD("usb_discovery_done\n");
- return ((MtpClient *)client_data)->usbDiscoveryDone();
-}
-
-} // namespace android
diff --git a/media/mtp/MtpClient.h b/media/mtp/MtpClient.h
deleted file mode 100644
index fa5c527..0000000
--- a/media/mtp/MtpClient.h
+++ /dev/null
@@ -1,68 +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.
- */
-
-#ifndef _MTP_CLIENT_H
-#define _MTP_CLIENT_H
-
-#include "MtpTypes.h"
-
-#include <utils/threads.h>
-
-struct usb_host_context;
-
-namespace android {
-
-class MtpClientThread;
-
-class MtpClient {
-private:
- MtpDeviceList mDeviceList;
- MtpClientThread* mThread;
- Condition mThreadStartCondition;
- Mutex mMutex;
- struct usb_host_context* mUsbHostContext;
- bool mDone;
-
-public:
- MtpClient();
- virtual ~MtpClient();
-
- bool start();
- void stop();
-
- inline MtpDeviceList& getDeviceList() { return mDeviceList; }
- MtpDevice* getDevice(int id);
-
-
- virtual void deviceAdded(MtpDevice *device) = 0;
- virtual void deviceRemoved(MtpDevice *device) = 0;
-
-private:
- // these return true if we should stop monitoring USB and clean up
- bool usbDeviceAdded(const char *devname);
- bool usbDeviceRemoved(const char *devname);
- bool usbDiscoveryDone();
-
- friend class MtpClientThread;
- bool threadLoop();
- static int usb_device_added(const char *devname, void* client_data);
- static int usb_device_removed(const char *devname, void* client_data);
- static int usb_discovery_done(void* client_data);
-};
-
-}; // namespace android
-
-#endif // _MTP_CLIENT_H
diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp
index d22c72f..fb1b073 100644
--- a/media/mtp/MtpDevice.cpp
+++ b/media/mtp/MtpDevice.cpp
@@ -38,6 +38,136 @@
namespace android {
+static bool isMtpDevice(uint16_t vendor, uint16_t product) {
+ // Sandisk Sansa Fuze
+ if (vendor == 0x0781 && product == 0x74c2)
+ return true;
+ // Samsung YP-Z5
+ if (vendor == 0x04e8 && product == 0x503c)
+ return true;
+ return false;
+}
+
+MtpDevice* MtpDevice::open(const char* deviceName, int fd) {
+ struct usb_device *device = usb_device_new(deviceName, fd);
+ if (!device) {
+ LOGE("usb_device_new failed for %s", deviceName);
+ return NULL;
+ }
+
+ struct usb_descriptor_header* desc;
+ struct usb_descriptor_iter iter;
+
+ usb_descriptor_iter_init(device, &iter);
+
+ while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+ if (desc->bDescriptorType == USB_DT_INTERFACE) {
+ struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+
+ if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
+ interface->bInterfaceSubClass == 1 && // Still Image Capture
+ interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470)
+ {
+ char* manufacturerName = usb_device_get_manufacturer_name(device);
+ char* productName = usb_device_get_product_name(device);
+ LOGD("Found camera: \"%s\" \"%s\"\n", manufacturerName, productName);
+ free(manufacturerName);
+ free(productName);
+ } else if (interface->bInterfaceClass == 0xFF &&
+ interface->bInterfaceSubClass == 0xFF &&
+ interface->bInterfaceProtocol == 0) {
+ char* interfaceName = usb_device_get_string(device, interface->iInterface);
+ if (!interfaceName) {
+ continue;
+ } else if (strcmp(interfaceName, "MTP")) {
+ free(interfaceName);
+ continue;
+ }
+ free(interfaceName);
+
+ // Looks like an android style MTP device
+ char* manufacturerName = usb_device_get_manufacturer_name(device);
+ char* productName = usb_device_get_product_name(device);
+ LOGD("Found MTP device: \"%s\" \"%s\"\n", manufacturerName, productName);
+ free(manufacturerName);
+ free(productName);
+ } else {
+ // look for special cased devices based on vendor/product ID
+ // we are doing this mainly for testing purposes
+ uint16_t vendor = usb_device_get_vendor_id(device);
+ uint16_t product = usb_device_get_product_id(device);
+ if (!isMtpDevice(vendor, product)) {
+ // not an MTP or PTP device
+ continue;
+ }
+ // request MTP OS string and descriptor
+ // some music players need to see this before entering MTP mode.
+ char buffer[256];
+ memset(buffer, 0, sizeof(buffer));
+ int ret = usb_device_control_transfer(device,
+ USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD,
+ USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE,
+ 0, buffer, sizeof(buffer), 0);
+ printf("usb_device_control_transfer returned %d errno: %d\n", ret, errno);
+ if (ret > 0) {
+ printf("got MTP string %s\n", buffer);
+ ret = usb_device_control_transfer(device,
+ USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1,
+ 0, 4, buffer, sizeof(buffer), 0);
+ printf("OS descriptor got %d\n", ret);
+ } else {
+ printf("no MTP string\n");
+ }
+ }
+
+ // if we got here, then we have a likely MTP or PTP device
+
+ // interface should be followed by three endpoints
+ struct usb_endpoint_descriptor *ep;
+ struct usb_endpoint_descriptor *ep_in_desc = NULL;
+ struct usb_endpoint_descriptor *ep_out_desc = NULL;
+ struct usb_endpoint_descriptor *ep_intr_desc = NULL;
+ for (int i = 0; i < 3; i++) {
+ ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+ if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
+ LOGE("endpoints not found\n");
+ usb_device_close(device);
+ return NULL;
+ }
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+ if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+ ep_in_desc = ep;
+ else
+ ep_out_desc = ep;
+ } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
+ ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+ ep_intr_desc = ep;
+ }
+ }
+ if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
+ LOGE("endpoints not found\n");
+ usb_device_close(device);
+ return NULL;
+ }
+
+ if (usb_device_claim_interface(device, interface->bInterfaceNumber)) {
+ LOGE("usb_device_claim_interface failed errno: %d\n", errno);
+ usb_device_close(device);
+ return NULL;
+ }
+
+ MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber,
+ ep_in_desc, ep_out_desc, ep_intr_desc);
+ mtpDevice->initialize();
+ return mtpDevice;
+ }
+ }
+
+ usb_device_close(device);
+ LOGE("device not found");
+ return NULL;
+}
+
MtpDevice::MtpDevice(struct usb_device* device, int interface,
const struct usb_endpoint_descriptor *ep_in,
const struct usb_endpoint_descriptor *ep_out,
@@ -49,7 +179,6 @@ MtpDevice::MtpDevice(struct usb_device* device, int interface,
mRequestOut(NULL),
mRequestIntr(NULL),
mDeviceInfo(NULL),
- mID(usb_device_get_unique_id(device)),
mSessionID(0),
mTransactionID(0),
mReceivedResponse(false)
@@ -106,6 +235,7 @@ void MtpDevice::print() {
MtpProperty* property = getDevicePropDesc(propCode);
if (property) {
property->print();
+ delete property;
}
}
}
@@ -122,11 +252,13 @@ void MtpDevice::print() {
for (int j = 0; j < props->size(); j++) {
MtpObjectProperty prop = (*props)[j];
MtpProperty* property = getObjectPropDesc(prop, format);
- if (property)
+ if (property) {
property->print();
- else
+ delete property;
+ } else {
LOGE("could not fetch property: %s",
MtpDebug::getObjectPropCodeName(prop));
+ }
}
}
}
@@ -362,18 +494,24 @@ bool MtpDevice::deleteObject(MtpObjectHandle handle) {
MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) {
MtpObjectInfo* info = getObjectInfo(handle);
- if (info)
- return info->mParent;
- else
+ if (info) {
+ MtpObjectHandle parent = info->mParent;
+ delete info;
+ return parent;
+ } else {
return -1;
+ }
}
MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) {
MtpObjectInfo* info = getObjectInfo(handle);
- if (info)
- return info->mStorageID;
- else
+ if (info) {
+ MtpObjectHandle storageId = info->mStorageID;
+ delete info;
+ return storageId;
+ } else {
return -1;
+ }
}
MtpObjectPropertyList* MtpDevice::getObjectPropsSupported(MtpObjectFormat format) {
@@ -430,6 +568,98 @@ MtpProperty* MtpDevice::getObjectPropDesc(MtpObjectProperty code, MtpObjectForma
return NULL;
}
+bool MtpDevice::readObject(MtpObjectHandle handle,
+ bool (* callback)(void* data, int offset, int length, void* clientData),
+ int objectSize, void* clientData) {
+ Mutex::Autolock autoLock(mMutex);
+ bool result = false;
+
+ mRequest.reset();
+ mRequest.setParameter(1, handle);
+ if (sendRequest(MTP_OPERATION_GET_OBJECT)
+ && mData.readDataHeader(mRequestIn1)) {
+ uint32_t length = mData.getContainerLength();
+ if (length - MTP_CONTAINER_HEADER_SIZE != objectSize) {
+ LOGE("readObject error objectSize: %d, length: %d",
+ objectSize, length);
+ goto fail;
+ }
+ length -= MTP_CONTAINER_HEADER_SIZE;
+ uint32_t remaining = length;
+ int offset = 0;
+
+ int initialDataLength = 0;
+ void* initialData = mData.getData(initialDataLength);
+ if (initialData) {
+ if (initialDataLength > 0) {
+ if (!callback(initialData, 0, initialDataLength, clientData))
+ goto fail;
+ remaining -= initialDataLength;
+ offset += initialDataLength;
+ }
+ free(initialData);
+ }
+
+ // USB reads greater than 16K don't work
+ char buffer1[16384], buffer2[16384];
+ mRequestIn1->buffer = buffer1;
+ mRequestIn2->buffer = buffer2;
+ struct usb_request* req = mRequestIn1;
+ void* writeBuffer = NULL;
+ int writeLength = 0;
+
+ while (remaining > 0 || writeBuffer) {
+ if (remaining > 0) {
+ // queue up a read request
+ req->buffer_length = (remaining > sizeof(buffer1) ? sizeof(buffer1) : remaining);
+ if (mData.readDataAsync(req)) {
+ LOGE("readDataAsync failed");
+ goto fail;
+ }
+ } else {
+ req = NULL;
+ }
+
+ if (writeBuffer) {
+ // write previous buffer
+ if (!callback(writeBuffer, offset, writeLength, clientData)) {
+ LOGE("write failed");
+ // wait for pending read before failing
+ if (req)
+ mData.readDataWait(mDevice);
+ goto fail;
+ }
+ offset += writeLength;
+ writeBuffer = NULL;
+ }
+
+ // wait for read to complete
+ if (req) {
+ int read = mData.readDataWait(mDevice);
+ if (read < 0)
+ goto fail;
+
+ if (read > 0) {
+ writeBuffer = req->buffer;
+ writeLength = read;
+ remaining -= read;
+ req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
+ } else {
+ writeBuffer = NULL;
+ }
+ }
+ }
+
+ MtpResponseCode response = readResponse();
+ if (response == MTP_RESPONSE_OK)
+ result = true;
+ }
+
+fail:
+ return result;
+}
+
+
// reads the object's data and writes it to the specified file path
bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int group, int perm) {
LOGD("readObject: %s", destPath);
@@ -462,8 +692,10 @@ bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int gro
void* initialData = mData.getData(initialDataLength);
if (initialData) {
if (initialDataLength > 0) {
- if (write(fd, initialData, initialDataLength) != initialDataLength)
+ if (write(fd, initialData, initialDataLength) != initialDataLength) {
+ free(initialData);
goto fail;
+ }
remaining -= initialDataLength;
}
free(initialData);
@@ -507,10 +739,14 @@ bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int gro
if (read < 0)
goto fail;
- writeBuffer = req->buffer;
- writeLength = read;
- remaining -= read;
- req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
+ if (read > 0) {
+ writeBuffer = req->buffer;
+ writeLength = read;
+ remaining -= read;
+ req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
+ } else {
+ writeBuffer = NULL;
+ }
}
}
diff --git a/media/mtp/MtpDevice.h b/media/mtp/MtpDevice.h
index d0a0fb3..b69203e 100644
--- a/media/mtp/MtpDevice.h
+++ b/media/mtp/MtpDevice.h
@@ -45,9 +45,6 @@ private:
MtpDeviceInfo* mDeviceInfo;
MtpPropertyList mDeviceProperties;
- // a unique ID for the device
- int mID;
-
// current session ID
MtpSessionID mSessionID;
// current transaction ID
@@ -67,9 +64,10 @@ public:
const struct usb_endpoint_descriptor *ep_in,
const struct usb_endpoint_descriptor *ep_out,
const struct usb_endpoint_descriptor *ep_intr);
- virtual ~MtpDevice();
- inline int getID() const { return mID; }
+ static MtpDevice* open(const char* deviceName, int fd);
+
+ virtual ~MtpDevice();
void initialize();
void close();
@@ -97,7 +95,11 @@ public:
MtpProperty* getDevicePropDesc(MtpDeviceProperty code);
MtpProperty* getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format);
- bool readObject(MtpObjectHandle handle, const char* destPath, int group,
+ bool readObject(MtpObjectHandle handle,
+ bool (* callback)(void* data, int offset,
+ int length, void* clientData),
+ int objectSize, void* clientData);
+ bool readObject(MtpObjectHandle handle, const char* destPath, int group,
int perm);
private:
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index be004d2..853a5af 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -343,8 +343,9 @@ MtpResponseCode MtpServer::doGetDeviceInfo() {
mData.putAUInt16(deviceProperties); // Device Properties Supported
mData.putAUInt16(captureFormats); // Capture Formats
mData.putAUInt16(playbackFormats); // Playback Formats
- // FIXME
- string.set("Google, Inc.");
+
+ property_get("ro.product.manufacturer", prop_value, "unknown manufacturer");
+ string.set(prop_value);
mData.putString(string); // Manufacturer
property_get("ro.product.model", prop_value, "MTP Device");
diff --git a/media/tests/CameraBrowser/Android.mk b/media/tests/CameraBrowser/Android.mk
new file mode 100644
index 0000000..1d81129
--- /dev/null
+++ b/media/tests/CameraBrowser/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CameraBrowser
+
+include $(BUILD_PACKAGE)
diff --git a/media/tests/CameraBrowser/AndroidManifest.xml b/media/tests/CameraBrowser/AndroidManifest.xml
new file mode 100644
index 0000000..f167f4b
--- /dev/null
+++ b/media/tests/CameraBrowser/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.camerabrowser">
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_USB" />
+
+ <application android:label="@string/app_label"
+ android:name="com.android.camerabrowser.CameraBrowserApplication">
+
+ <activity android:name="CameraBrowser" android:label="Camera Browser">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="StorageBrowser" />
+ <activity android:name="ObjectBrowser" />
+ <activity android:name="ObjectViewer" />
+
+<!--
+ <receiver android:name="UsbReceiver">
+ <intent-filter>
+ <action android:name="android.hardware.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ </receiver>
+-->
+ </application>
+
+
+</manifest>
diff --git a/media/tests/CameraBrowser/res/layout/object_info.xml b/media/tests/CameraBrowser/res/layout/object_info.xml
new file mode 100644
index 0000000..a0499f2
--- /dev/null
+++ b/media/tests/CameraBrowser/res/layout/object_info.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/object_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <TableRow>
+ <TextView android:id="@+id/name_label"
+ android:text="@string/name_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/name"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/size_label"
+ android:text="@string/size_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/size"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/format_label"
+ android:text="@string/format_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/format"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/thumb_width_label"
+ android:text="@string/thumb_width_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/thumb_width"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/thumb_height_label"
+ android:text="@string/thumb_height_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/thumb_height"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/thumb_size_label"
+ android:text="@string/thumb_size_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/thumb_size"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/width_label"
+ android:text="@string/width_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/width"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/height_label"
+ android:text="@string/height_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/height"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/depth_label"
+ android:text="@string/depth_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/depth"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/sequence_label"
+ android:text="@string/sequence_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/sequence"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/created_label"
+ android:text="@string/created_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/created"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/modified_label"
+ android:text="@string/modified_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/modified"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <TextView android:id="@+id/keywords_label"
+ android:text="@string/keywords_label"
+ android:layout_gravity="right"
+ android:layout_marginRight="8dip"
+ style="@style/info_label" />
+
+ <TextView android:id="@+id/keywords"
+ style="@style/info_value" />
+ </TableRow>
+ <TableRow>
+ <ImageView android:id="@+id/thumbnail" />
+ </TableRow>
+ <TableRow>
+ <Button android:id="@+id/import_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/import_label">
+ </Button>
+ <Button android:id="@+id/delete_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/delete_label">
+ </Button>
+ </TableRow>
+</TableLayout>
+
diff --git a/media/tests/CameraBrowser/res/layout/object_list.xml b/media/tests/CameraBrowser/res/layout/object_list.xml
new file mode 100644
index 0000000..30c18bb
--- /dev/null
+++ b/media/tests/CameraBrowser/res/layout/object_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ImageView android:id="@+id/thumbnail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center_vertical"
+ android:paddingLeft="6dip"
+ android:minHeight="?android:attr/listPreferredItemHeight" />
+</LinearLayout>
diff --git a/media/tests/CameraBrowser/res/values/strings.xml b/media/tests/CameraBrowser/res/values/strings.xml
new file mode 100644
index 0000000..932aaec
--- /dev/null
+++ b/media/tests/CameraBrowser/res/values/strings.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <string name="app_label">Camera Browser</string>
+
+ <!-- for object info -->
+ <string name="name_label">Name: </string>
+ <string name="size_label">Size: </string>
+ <string name="format_label">Format: </string>
+ <string name="thumb_width_label">Thumb Width: </string>
+ <string name="thumb_height_label">Thumb Height: </string>
+ <string name="thumb_size_label">Thumb Size: </string>
+ <string name="width_label">Width: </string>
+ <string name="height_label">Height: </string>
+ <string name="depth_label">Depth: </string>
+ <string name="sequence_label">Sequence: </string>
+ <string name="created_label">Created: </string>
+ <string name="modified_label">Modified: </string>
+ <string name="keywords_label">Keywords: </string>
+
+ <!-- button labels -->
+ <string name="import_label">Import</string>
+ <string name="delete_label">Delete</string>
+
+ <!-- toasts -->
+ <string name="object_saved_message">Object saved</string>
+ <string name="save_failed_message">Could not save object</string>
+ <string name="object_deleted_message">Object deleted</string>
+ <string name="delete_failed_message">Could not delete object</string>
+ <string name="start_activity_failed_message">Import succeeded, but could not display object</string>
+
+</resources>
diff --git a/media/tests/CameraBrowser/res/values/styles.xml b/media/tests/CameraBrowser/res/values/styles.xml
new file mode 100644
index 0000000..c869985
--- /dev/null
+++ b/media/tests/CameraBrowser/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<resources>
+ <style name="info_label">
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:paddingRight">4dip</item>
+ </style>
+
+ <style name="info_value">
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textStyle">normal</item>
+ </style>
+
+</resources>
+
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
new file mode 100644
index 0000000..f642d93
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.mtp.MtpClient;
+import android.mtp.MtpDevice;
+import android.mtp.MtpDeviceInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TwoLineListItem;
+
+import java.util.List;
+
+ /**
+ * A list view displaying all connected cameras.
+ */
+public class CameraBrowser extends ListActivity implements MtpClient.Listener {
+
+ private static final String TAG = "CameraBrowser";
+
+ private MtpClient mClient;
+ private List<MtpDevice> mDeviceList;
+
+ private static final int MODEL_COLUMN = 0;
+ private static final int MANUFACTURER_COLUMN = 1;
+ private static final int COLUMN_COUNT = 2;
+
+ private class CameraAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+
+ public CameraAdapter(Context c) {
+ mContext = c;
+ mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public int getCount() {
+ return mDeviceList.size();
+ }
+
+ public Object getItem(int position) {
+ return mDeviceList.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TwoLineListItem view;
+ if (convertView == null) {
+ view = (TwoLineListItem)mInflater.inflate(
+ android.R.layout.simple_list_item_2, parent, false);
+ } else {
+ view = (TwoLineListItem)convertView;
+ }
+
+ TextView textView1 = (TextView)view.findViewById(com.android.internal.R.id.text1);
+ TextView textView2 = (TextView)view.findViewById(com.android.internal.R.id.text2);
+ MtpDevice device = mDeviceList.get(position);
+ MtpDeviceInfo info = device.getDeviceInfo();
+ if (info != null) {
+ textView1.setText(info.getManufacturer());
+ textView2.setText(info.getModel());
+ } else {
+ textView1.setText("???");
+ textView2.setText("???");
+ }
+ return view;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = ((CameraBrowserApplication)getApplication()).getMtpClient();
+ mClient.addListener(this);
+ mDeviceList = mClient.getDeviceList();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ reload();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mClient.removeListener(this);
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent intent = new Intent(this, StorageBrowser.class);
+ intent.putExtra("device", mDeviceList.get(position).getDeviceName());
+ startActivity(intent);
+ }
+
+ private void reload() {
+ setListAdapter(new CameraAdapter(this));
+ }
+
+ public void deviceAdded(MtpDevice device) {
+ Log.d(TAG, "deviceAdded: " + device.getDeviceName());
+ mDeviceList = mClient.getDeviceList();
+ reload();
+ }
+
+ public void deviceRemoved(MtpDevice device) {
+ Log.d(TAG, "deviceRemoved: " + device.getDeviceName());
+ mDeviceList = mClient.getDeviceList();
+ reload();
+ }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java
new file mode 100644
index 0000000..6f1edfea
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.Application;
+import android.mtp.MtpClient;
+
+
+public class CameraBrowserApplication extends Application {
+
+ private MtpClient mClient;
+
+ @Override
+ public void onCreate() {
+ mClient = new MtpClient(this);
+ }
+
+ @Override
+ public void onTerminate() {
+ mClient.close();
+ mClient = null;
+ }
+
+ public MtpClient getMtpClient() {
+ return mClient;
+ }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java
new file mode 100644
index 0000000..736af1f
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.UsbManager;
+import android.util.Log;
+
+public class DeviceDisconnectedReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "DeviceDisconnectedReceiver";
+
+ private final Activity mActivity;
+ private final String mDeviceName;
+
+ public DeviceDisconnectedReceiver(Activity activity, String deviceName) {
+ mActivity = activity;
+ mDeviceName = deviceName;
+
+ IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ activity.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String deviceName = intent.getStringExtra(UsbManager.EXTRA_DEVICE_NAME);
+ Log.d(TAG, "ACTION_USB_DEVICE_DETACHED " + deviceName);
+
+ // close our activity if the device it is displaying is disconnected
+ if (deviceName.equals(mDeviceName)) {
+ mActivity.finish();
+ }
+ }
+} \ No newline at end of file
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
new file mode 100644
index 0000000..82251d9
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.mtp.MtpClient;
+import android.mtp.MtpConstants;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+ /**
+ * A list view displaying all objects within a container (folder or storage unit).
+ */
+public class ObjectBrowser extends ListActivity {
+
+ private static final String TAG = "ObjectBrowser";
+
+ private MtpClient mClient;
+ private List<MtpObjectInfo> mObjectList;
+ private String mDeviceName;
+ private int mStorageID;
+ private int mObjectID;
+ private DeviceDisconnectedReceiver mDisconnectedReceiver;
+
+ private class ObjectAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+
+ public ObjectAdapter(Context c) {
+ mContext = c;
+ mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public int getCount() {
+ if (mObjectList == null) {
+ return 0;
+ } else {
+ return mObjectList.size();
+ }
+ }
+
+ public Object getItem(int position) {
+ return mObjectList.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.object_list, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ TextView nameView = (TextView)view.findViewById(R.id.name);
+ MtpObjectInfo info = mObjectList.get(position);
+ nameView.setText(info.getName());
+
+ int thumbFormat = info.getThumbFormat();
+ if (thumbFormat == MtpConstants.FORMAT_EXIF_JPEG
+ || thumbFormat == MtpConstants.FORMAT_JFIF) {
+ byte[] thumbnail = mClient.getThumbnail(mDeviceName, info.getObjectHandle());
+ if (thumbnail != null) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
+ if (bitmap != null) {
+ ImageView thumbView = (ImageView)view.findViewById(R.id.thumbnail);
+ thumbView.setImageBitmap(bitmap);
+ }
+ }
+ }
+ return view;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mClient = ((CameraBrowserApplication)getApplication()).getMtpClient();
+ mDeviceName = getIntent().getStringExtra("device");
+ mStorageID = getIntent().getIntExtra("storage", 0);
+ mObjectID = getIntent().getIntExtra("object", 0);
+ mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mObjectList = mClient.getObjectList(mDeviceName, mStorageID, mObjectID);
+ setListAdapter(new ObjectAdapter(this));
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mDisconnectedReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ MtpObjectInfo info = mObjectList.get(position);
+ Intent intent;
+ if (info.getFormat() == MtpConstants.FORMAT_ASSOCIATION) {
+ intent = new Intent(this, ObjectBrowser.class);
+ } else {
+ intent = new Intent(this, ObjectViewer.class);
+ }
+ intent.putExtra("device", mDeviceName);
+ intent.putExtra("storage", mStorageID);
+ intent.putExtra("object", info.getObjectHandle());
+ startActivity(intent);
+ }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
new file mode 100644
index 0000000..e9ea9f3
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.mtp.MtpClient;
+import android.mtp.MtpConstants;
+import android.mtp.MtpObjectInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * A view to display the properties of an object.
+ */
+public class ObjectViewer extends Activity implements View.OnClickListener {
+
+ private static final String TAG = "ObjectViewer";
+
+ private MtpClient mClient;
+ private String mDeviceName;
+ private int mStorageID;
+ private int mObjectID;
+ private String mFileName;
+ private Button mImportButton;
+ private Button mDeleteButton;
+ private DeviceDisconnectedReceiver mDisconnectedReceiver;
+
+ private final class ScannerClient implements MediaScannerConnectionClient {
+ private final Context mContext;
+ private String mPath;
+
+ public ScannerClient(Context context) {
+ mContext = context;
+ }
+
+ public void setScanPath(String path) {
+ mPath = path;
+ }
+
+ @Override
+ public void onMediaScannerConnected() {
+ mScannerConnection.scanFile(mPath, null);
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ mScannerConnection.disconnect();
+
+ // try to start an activity to view the file
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.start_activity_failed_message,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private MediaScannerConnection mScannerConnection;
+ private ScannerClient mScannerClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mClient = ((CameraBrowserApplication)getApplication()).getMtpClient();
+
+ setContentView(R.layout.object_info);
+
+ mImportButton = (Button)findViewById(R.id.import_button);
+ mImportButton.setOnClickListener(this);
+ mDeleteButton = (Button)findViewById(R.id.delete_button);
+ mDeleteButton.setOnClickListener(this);
+
+ mDeviceName = getIntent().getStringExtra("device");
+ mStorageID = getIntent().getIntExtra("storage", 0);
+ mObjectID = getIntent().getIntExtra("object", 0);
+ mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName);
+ mScannerClient = new ScannerClient(this);
+ mScannerConnection = new MediaScannerConnection(this, mScannerClient);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ MtpObjectInfo info = mClient.getObjectInfo(mDeviceName, mObjectID);
+ if (info != null) {
+ TextView view = (TextView)findViewById(R.id.name);
+ mFileName = info.getName();
+ view.setText(mFileName);
+ view = (TextView)findViewById(R.id.format);
+ view.setText(Integer.toHexString(info.getFormat()).toUpperCase());
+ view = (TextView)findViewById(R.id.size);
+ view.setText(Long.toString(info.getCompressedSize()));
+ view = (TextView)findViewById(R.id.thumb_width);
+ view.setText(Long.toString(info.getThumbPixWidth()));
+ view = (TextView)findViewById(R.id.thumb_height);
+ view.setText(Long.toString(info.getThumbPixHeight()));
+ view = (TextView)findViewById(R.id.thumb_size);
+ view.setText(Long.toString(info.getThumbCompressedSize()));
+ view = (TextView)findViewById(R.id.width);
+ view.setText(Long.toString(info.getImagePixWidth()));
+ view = (TextView)findViewById(R.id.height);
+ view.setText(Long.toString(info.getImagePixHeight()));
+ view = (TextView)findViewById(R.id.depth);
+ view.setText(Long.toString(info.getImagePixDepth()));
+ view = (TextView)findViewById(R.id.sequence);
+ view.setText(Long.toString(info.getSequenceNumber()));
+ view = (TextView)findViewById(R.id.created);
+ Date date = new Date(info.getDateCreated() * 1000);
+ view.setText(date.toString());
+ view = (TextView)findViewById(R.id.modified);
+ date = new Date(info.getDateModified() * 1000);
+ view.setText(date.toString());
+ view = (TextView)findViewById(R.id.keywords);
+ view.setText(info.getKeywords());
+ int thumbFormat = info.getThumbFormat();
+ if (thumbFormat == MtpConstants.FORMAT_EXIF_JPEG
+ || thumbFormat == MtpConstants.FORMAT_JFIF) {
+ byte[] thumbnail = mClient.getThumbnail(mDeviceName, info.getObjectHandle());
+ if (thumbnail != null) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
+ if (bitmap != null) {
+ ImageView thumbView = (ImageView)findViewById(R.id.thumbnail);
+ thumbView.setImageBitmap(bitmap);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mDisconnectedReceiver);
+ super.onDestroy();
+ }
+
+ private void importObject() {
+ // copy file to /mnt/sdcard/imported/<filename>
+ File dest = Environment.getExternalStorageDirectory();
+ dest = new File(dest, "imported");
+ dest.mkdirs();
+ dest = new File(dest, mFileName);
+
+ if (mClient.importFile(mDeviceName, mObjectID, dest.getAbsolutePath())) {
+ Toast.makeText(this, R.string.object_saved_message, Toast.LENGTH_SHORT).show();
+
+ mScannerClient.setScanPath(dest.getAbsolutePath());
+ mScannerConnection.connect();
+ } else {
+ Toast.makeText(this, R.string.save_failed_message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void deleteObject() {
+ if (mClient.deleteObject(mDeviceName, mObjectID)) {
+ Toast.makeText(this, R.string.object_deleted_message, Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ Toast.makeText(this, R.string.delete_failed_message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public void onClick(View v) {
+ if (v == mImportButton) {
+ importObject();
+ } else if (v == mDeleteButton) {
+ deleteObject();
+ }
+ }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
new file mode 100644
index 0000000..7d5a5da
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.mtp.MtpClient;
+import android.mtp.MtpDevice;
+import android.mtp.MtpStorageInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * A list view displaying all storage units on a device.
+ */
+public class StorageBrowser extends ListActivity {
+
+ private static final String TAG = "StorageBrowser";
+
+ private MtpClient mClient;
+ private String mDeviceName;
+ private List<MtpStorageInfo> mStorageList;
+ private DeviceDisconnectedReceiver mDisconnectedReceiver;
+
+ private class StorageAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+
+ public StorageAdapter(Context c) {
+ mContext = c;
+ mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public int getCount() {
+ if (mStorageList == null) {
+ return 0;
+ } else {
+ return mStorageList.size();
+ }
+ }
+
+ public Object getItem(int position) {
+ return mStorageList.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+ if (convertView == null) {
+ view = (TextView)mInflater.inflate(
+ android.R.layout.simple_list_item_1, parent, false);
+ } else {
+ view = (TextView)convertView;
+ }
+
+ MtpStorageInfo info = mStorageList.get(position);
+ if (info != null) {
+ view.setText(info.getDescription());
+ } else {
+ view.setText("???");
+ }
+ return view;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mClient = ((CameraBrowserApplication)getApplication()).getMtpClient();
+ mDeviceName = getIntent().getStringExtra("device");
+ mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mStorageList = mClient.getStorageList(mDeviceName);
+ setListAdapter(new StorageAdapter(this));
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mDisconnectedReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent intent = new Intent(this, ObjectBrowser.class);
+ intent.putExtra("device", mDeviceName);
+ intent.putExtra("storage", mStorageList.get(position).getStorageId());
+ startActivity(intent);
+ }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java
new file mode 100644
index 0000000..22d9443
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package com.android.camerabrowser;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.hardware.UsbDevice;
+import android.hardware.UsbManager;
+import android.mtp.MtpClient;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+
+public class UsbReceiver extends BroadcastReceiver
+{
+ private static final String TAG = "UsbReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive " + intent);
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (MtpClient.isCamera(device)) {
+ String deviceName = device.getDeviceName();
+ Log.d(TAG, "Got camera: " + deviceName);
+ intent = new Intent(context, StorageBrowser.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("device", deviceName);
+ context.startActivity(intent);
+ }
+ }
+ }
+}