1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media; 18 19import android.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.res.AssetFileDescriptor; 25import android.media.MediaCodec; 26import android.media.MediaFormat; 27import android.media.MediaHTTPService; 28import android.net.Uri; 29import android.os.IBinder; 30 31import java.io.FileDescriptor; 32import java.io.IOException; 33import java.lang.annotation.Retention; 34import java.lang.annotation.RetentionPolicy; 35import java.nio.ByteBuffer; 36import java.nio.ByteOrder; 37import java.util.HashMap; 38import java.util.Map; 39import java.util.UUID; 40 41/** 42 * MediaExtractor facilitates extraction of demuxed, typically encoded, media data 43 * from a data source. 44 * <p>It is generally used like this: 45 * <pre> 46 * MediaExtractor extractor = new MediaExtractor(); 47 * extractor.setDataSource(...); 48 * int numTracks = extractor.getTrackCount(); 49 * for (int i = 0; i < numTracks; ++i) { 50 * MediaFormat format = extractor.getTrackFormat(i); 51 * String mime = format.getString(MediaFormat.KEY_MIME); 52 * if (weAreInterestedInThisTrack) { 53 * extractor.selectTrack(i); 54 * } 55 * } 56 * ByteBuffer inputBuffer = ByteBuffer.allocate(...) 57 * while (extractor.readSampleData(inputBuffer, ...) >= 0) { 58 * int trackIndex = extractor.getSampleTrackIndex(); 59 * long presentationTimeUs = extractor.getSampleTime(); 60 * ... 61 * extractor.advance(); 62 * } 63 * 64 * extractor.release(); 65 * extractor = null; 66 * </pre> 67 */ 68final public class MediaExtractor { 69 public MediaExtractor() { 70 native_setup(); 71 } 72 73 /** 74 * Sets the data source (MediaDataSource) to use. 75 * 76 * @param dataSource the MediaDataSource for the media you want to extract from 77 * 78 * @throws IllegalArgumentException if dataSource is invalid. 79 */ 80 public native final void setDataSource(@NonNull MediaDataSource dataSource) 81 throws IOException; 82 83 /** 84 * Sets the data source as a content Uri. 85 * 86 * @param context the Context to use when resolving the Uri 87 * @param uri the Content URI of the data you want to extract from. 88 * @param headers the headers to be sent together with the request for the data. 89 * This can be {@code null} if no specific headers are to be sent with the 90 * request. 91 */ 92 public final void setDataSource( 93 @NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers) 94 throws IOException { 95 String scheme = uri.getScheme(); 96 if (scheme == null || scheme.equals("file")) { 97 setDataSource(uri.getPath()); 98 return; 99 } 100 101 AssetFileDescriptor fd = null; 102 try { 103 ContentResolver resolver = context.getContentResolver(); 104 fd = resolver.openAssetFileDescriptor(uri, "r"); 105 if (fd == null) { 106 return; 107 } 108 // Note: using getDeclaredLength so that our behavior is the same 109 // as previous versions when the content provider is returning 110 // a full file. 111 if (fd.getDeclaredLength() < 0) { 112 setDataSource(fd.getFileDescriptor()); 113 } else { 114 setDataSource( 115 fd.getFileDescriptor(), 116 fd.getStartOffset(), 117 fd.getDeclaredLength()); 118 } 119 return; 120 } catch (SecurityException ex) { 121 } catch (IOException ex) { 122 } finally { 123 if (fd != null) { 124 fd.close(); 125 } 126 } 127 128 setDataSource(uri.toString(), headers); 129 } 130 131 /** 132 * Sets the data source (file-path or http URL) to use. 133 * 134 * @param path the path of the file, or the http URL 135 * @param headers the headers associated with the http request for the stream you want to play. 136 * This can be {@code null} if no specific headers are to be sent with the 137 * request. 138 */ 139 public final void setDataSource(@NonNull String path, @Nullable Map<String, String> headers) 140 throws IOException { 141 String[] keys = null; 142 String[] values = null; 143 144 if (headers != null) { 145 keys = new String[headers.size()]; 146 values = new String[headers.size()]; 147 148 int i = 0; 149 for (Map.Entry<String, String> entry: headers.entrySet()) { 150 keys[i] = entry.getKey(); 151 values[i] = entry.getValue(); 152 ++i; 153 } 154 } 155 156 nativeSetDataSource( 157 MediaHTTPService.createHttpServiceBinderIfNecessary(path), 158 path, 159 keys, 160 values); 161 } 162 163 private native final void nativeSetDataSource( 164 @NonNull IBinder httpServiceBinder, 165 @NonNull String path, 166 @Nullable String[] keys, 167 @Nullable String[] values) throws IOException; 168 169 /** 170 * Sets the data source (file-path or http URL) to use. 171 * 172 * @param path the path of the file, or the http URL of the stream 173 * 174 * <p>When <code>path</code> refers to a local file, the file may actually be opened by a 175 * process other than the calling application. This implies that the pathname 176 * should be an absolute path (as any other process runs with unspecified current working 177 * directory), and that the pathname should reference a world-readable file. 178 * As an alternative, the application could first open the file for reading, 179 * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}. 180 */ 181 public final void setDataSource(@NonNull String path) throws IOException { 182 nativeSetDataSource( 183 MediaHTTPService.createHttpServiceBinderIfNecessary(path), 184 path, 185 null, 186 null); 187 } 188 189 /** 190 * Sets the data source (FileDescriptor) to use. It is the caller's responsibility 191 * to close the file descriptor. It is safe to do so as soon as this call returns. 192 * 193 * @param fd the FileDescriptor for the file you want to extract from. 194 */ 195 public final void setDataSource(@NonNull FileDescriptor fd) throws IOException { 196 setDataSource(fd, 0, 0x7ffffffffffffffL); 197 } 198 199 /** 200 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 201 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 202 * to close the file descriptor. It is safe to do so as soon as this call returns. 203 * 204 * @param fd the FileDescriptor for the file you want to extract from. 205 * @param offset the offset into the file where the data to be extracted starts, in bytes 206 * @param length the length in bytes of the data to be extracted 207 */ 208 public native final void setDataSource( 209 @NonNull FileDescriptor fd, long offset, long length) throws IOException; 210 211 @Override 212 protected void finalize() { 213 native_finalize(); 214 } 215 216 /** 217 * Make sure you call this when you're done to free up any resources 218 * instead of relying on the garbage collector to do this for you at 219 * some point in the future. 220 */ 221 public native final void release(); 222 223 /** 224 * Count the number of tracks found in the data source. 225 */ 226 public native final int getTrackCount(); 227 228 /** 229 * Get the PSSH info if present. 230 * @return a map of uuid-to-bytes, with the uuid specifying 231 * the crypto scheme, and the bytes being the data specific to that scheme. 232 * This can be {@code null} if the source does not contain PSSH info. 233 */ 234 @Nullable 235 public Map<UUID, byte[]> getPsshInfo() { 236 Map<UUID, byte[]> psshMap = null; 237 Map<String, Object> formatMap = getFileFormatNative(); 238 if (formatMap != null && formatMap.containsKey("pssh")) { 239 ByteBuffer rawpssh = (ByteBuffer) formatMap.get("pssh"); 240 rawpssh.order(ByteOrder.nativeOrder()); 241 rawpssh.rewind(); 242 formatMap.remove("pssh"); 243 // parse the flat pssh bytebuffer into something more manageable 244 psshMap = new HashMap<UUID, byte[]>(); 245 while (rawpssh.remaining() > 0) { 246 rawpssh.order(ByteOrder.BIG_ENDIAN); 247 long msb = rawpssh.getLong(); 248 long lsb = rawpssh.getLong(); 249 UUID uuid = new UUID(msb, lsb); 250 rawpssh.order(ByteOrder.nativeOrder()); 251 int datalen = rawpssh.getInt(); 252 byte [] psshdata = new byte[datalen]; 253 rawpssh.get(psshdata); 254 psshMap.put(uuid, psshdata); 255 } 256 } 257 return psshMap; 258 } 259 260 @NonNull 261 private native Map<String, Object> getFileFormatNative(); 262 263 /** 264 * Get the track format at the specified index. 265 * More detail on the representation can be found at {@link android.media.MediaCodec} 266 */ 267 @NonNull 268 public MediaFormat getTrackFormat(int index) { 269 return new MediaFormat(getTrackFormatNative(index)); 270 } 271 272 @NonNull 273 private native Map<String, Object> getTrackFormatNative(int index); 274 275 /** 276 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and 277 * {@link #getSampleTime} only retrieve information for the subset of tracks 278 * selected. 279 * Selecting the same track multiple times has no effect, the track is 280 * only selected once. 281 */ 282 public native void selectTrack(int index); 283 284 /** 285 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and 286 * {@link #getSampleTime} only retrieve information for the subset of tracks 287 * selected. 288 */ 289 public native void unselectTrack(int index); 290 291 /** 292 * If possible, seek to a sync sample at or before the specified time 293 */ 294 public static final int SEEK_TO_PREVIOUS_SYNC = 0; 295 /** 296 * If possible, seek to a sync sample at or after the specified time 297 */ 298 public static final int SEEK_TO_NEXT_SYNC = 1; 299 /** 300 * If possible, seek to the sync sample closest to the specified time 301 */ 302 public static final int SEEK_TO_CLOSEST_SYNC = 2; 303 304 /** @hide */ 305 @IntDef({ 306 SEEK_TO_PREVIOUS_SYNC, 307 SEEK_TO_NEXT_SYNC, 308 SEEK_TO_CLOSEST_SYNC, 309 }) 310 @Retention(RetentionPolicy.SOURCE) 311 public @interface SeekMode {} 312 313 /** 314 * All selected tracks seek near the requested time according to the 315 * specified mode. 316 */ 317 public native void seekTo(long timeUs, @SeekMode int mode); 318 319 /** 320 * Advance to the next sample. Returns false if no more sample data 321 * is available (end of stream). 322 */ 323 public native boolean advance(); 324 325 /** 326 * Retrieve the current encoded sample and store it in the byte buffer 327 * starting at the given offset. 328 * <p> 329 * <b>Note:</b>As of API 21, on success the position and limit of 330 * {@code byteBuf} is updated to point to the data just read. 331 * @param byteBuf the destination byte buffer 332 * @return the sample size (or -1 if no more samples are available). 333 */ 334 public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset); 335 336 /** 337 * Returns the track index the current sample originates from (or -1 338 * if no more samples are available) 339 */ 340 public native int getSampleTrackIndex(); 341 342 /** 343 * Returns the current sample's presentation time in microseconds. 344 * or -1 if no more samples are available. 345 */ 346 public native long getSampleTime(); 347 348 // Keep these in sync with their equivalents in NuMediaExtractor.h 349 /** 350 * The sample is a sync sample (or in {@link MediaCodec}'s terminology 351 * it is a key frame.) 352 * 353 * @see MediaCodec#BUFFER_FLAG_KEY_FRAME 354 */ 355 public static final int SAMPLE_FLAG_SYNC = 1; 356 357 /** 358 * The sample is (at least partially) encrypted, see also the documentation 359 * for {@link android.media.MediaCodec#queueSecureInputBuffer} 360 */ 361 public static final int SAMPLE_FLAG_ENCRYPTED = 2; 362 363 /** @hide */ 364 @IntDef( 365 flag = true, 366 value = { 367 SAMPLE_FLAG_SYNC, 368 SAMPLE_FLAG_ENCRYPTED, 369 }) 370 @Retention(RetentionPolicy.SOURCE) 371 public @interface SampleFlag {} 372 373 /** 374 * Returns the current sample's flags. 375 */ 376 @SampleFlag 377 public native int getSampleFlags(); 378 379 /** 380 * If the sample flags indicate that the current sample is at least 381 * partially encrypted, this call returns relevant information about 382 * the structure of the sample data required for decryption. 383 * @param info The android.media.MediaCodec.CryptoInfo structure 384 * to be filled in. 385 * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED} 386 */ 387 public native boolean getSampleCryptoInfo(@NonNull MediaCodec.CryptoInfo info); 388 389 /** 390 * Returns an estimate of how much data is presently cached in memory 391 * expressed in microseconds. Returns -1 if that information is unavailable 392 * or not applicable (no cache). 393 */ 394 public native long getCachedDuration(); 395 396 /** 397 * Returns true iff we are caching data and the cache has reached the 398 * end of the data stream (for now, a future seek may of course restart 399 * the fetching of data). 400 * This API only returns a meaningful result if {@link #getCachedDuration} 401 * indicates the presence of a cache, i.e. does NOT return -1. 402 */ 403 public native boolean hasCacheReachedEndOfStream(); 404 405 private static native final void native_init(); 406 private native final void native_setup(); 407 private native final void native_finalize(); 408 409 static { 410 System.loadLibrary("media_jni"); 411 native_init(); 412 } 413 414 private long mNativeContext; 415} 416