2013-03-19 38 views
5

Tôi muốn sử dụng Android MediaCodec để giải mã luồng video, sau đó sử dụng hình ảnh đầu ra để xử lý hình ảnh tiếp theo trong mã gốc.Vi phạm truy cập bằng mã gốc với bộ giải mã Android MediaCodec tăng tốc phần cứng

Nền tảng: ASUS tf700t android 4.1.1. Luồng thử nghiệm: H.264 full HD @ 24 frm/s

Với Tegra-3 SoC bên trong, tôi dựa vào phần cứng hỗ trợ giải mã video. Về mặt chức năng, ứng dụng của tôi hoạt động như mong đợi: Tôi thực sự có thể truy cập hình ảnh bộ giải mã và xử lý chúng đúng cách. Tuy nhiên, tôi trải nghiệm một tải CPU cpu rất cao.

Trong các thí nghiệm sau, tải quy trình/luồng được đo bằng "top -m 32 -t" trong trình bao adb. Để có được đầu ra đáng tin cậy từ "top", tất cả 4 lõi CPU được kích hoạt bằng cách chạy một vài chuỗi lặp mãi mãi ở mức ưu tiên thấp nhất. Điều này được xác nhận bằng cách thực thi liên tục "cat/sys/devices/system/cpu/cpu [0-3]/online". Để giữ cho mọi thứ đơn giản, chỉ có giải mã video, không có âm thanh; và không có điều khiển thời gian để bộ giải mã chạy nhanh nhất có thể.

Thử nghiệm đầu tiên: chạy ứng dụng, gọi hàm xử lý JNI, nhưng tất cả các cuộc gọi xử lý tiếp theo đều được nhận xét. Kết quả:

  • thông: 25 frm/s
  • 1% tải trọng của chủ đề VideoDecoder của ứng dụng
  • 24% tải trọng của chủ đề Binder_3 của quá trình/system/bin/mediaserver

Nó có vẻ như tốc độ giải mã là CPU bị giới hạn (25% của CPU quad-core) ... Khi kích hoạt xử lý đầu ra, hình ảnh được giải mã là chính xác và ứng dụng hoạt động. Chỉ có vấn đề: tải CPU quá cao để giải mã.

Sau hàng loạt thử nghiệm, tôi đã cân nhắc đưa cho MediaCodec một bề mặt để vẽ kết quả của nó. Trong tất cả các khía cạnh khác, mã giống hệt nhau. Kết quả:

  • thông 55 frm/s (đẹp !!)
  • 2% tải trọng của chủ đề VideoDecoder của ứng dụng
  • 1% tải trọng của chủ đề mediaserver của quá trình/system/bin/mediaserver

Thật vậy, video được hiển thị trên Bề mặt được cung cấp. Vì hầu như không có bất kỳ tải CPU nào, điều này phải được tăng tốc phần cứng ...

Dường như de MediaCodec chỉ sử dụng tăng tốc phần cứng nếu một Bề mặt được cung cấp?

Cho đến nay, rất tốt. Tôi đã có xu hướng sử dụng Surface như một công việc xung quanh (không bắt buộc, nhưng trong một số trường hợp thậm chí là một điều tốt đẹp để có). Nhưng, trong trường hợp một bề mặt được cung cấp, tôi không thể truy cập vào các hình ảnh đầu ra! Kết quả là vi phạm truy cập trong mã gốc.

Điều này thực sự giải đố tôi! Tôi không thấy bất kỳ khái niệm nào về các giới hạn truy cập, hoặc bất cứ điều gì trong tài liệu http://developer.android.com/reference/android/media/MediaCodec.html. Cũng không có gì theo hướng này được đề cập tại bản trình bày Google I/O http://www.youtube.com/watch?v=RQws6vsoav8.

Vậy: cách sử dụng bộ giải mã Android MediaCodec tăng tốc phần cứng và truy cập hình ảnh bằng mã gốc?Làm cách nào để tránh vi phạm quyền truy cập? Bất kỳ trợ giúp được đánh giá cao! Ngoài ra bất kỳ lời giải thích hoặc gợi ý.

Tôi khá chắc chắn MediaExtractor và MediaCodec được sử dụng đúng cách, vì ứng dụng có chức năng ok (miễn là tôi không cung cấp Bề mặt). Nó vẫn còn khá thực nghiệm, và một thiết kế API tốt nằm trong danh sách todo ;-)

Lưu ý rằng sự khác biệt duy nhất giữa hai thí nghiệm là biến mSurface: null hoặc một bề mặt thực tế trong "mDecoder.configure (mediaFormat , mSurface, null, 0); "

đang khởi:

mExtractor = new MediaExtractor(); 
mExtractor.setDataSource(mPath); 

// Locate first video stream 
for (int i = 0; i < mExtractor.getTrackCount(); i++) { 
    mediaFormat = mExtractor.getTrackFormat(i); 
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME); 
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime)); 
    if (streamId == -1 && mime.startsWith("video/")) { 
     streamId = i; 
    } 
} 

if (streamId == -1) { 
    Log.e(TAG, "Can't find video info in " + mPath); 
    return; 
} 

mExtractor.selectTrack(streamId); 
mediaFormat = mExtractor.getTrackFormat(streamId); 

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); 
mDecoder.configure(mediaFormat, mSurface, null, 0); 

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); 
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); 
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString())); 
JniGlue.decoutStart(width, height); 

Decoder vòng (chạy trong một thread riêng biệt):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); 
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); 

while (!isEOS && !Thread.interrupted()) { 
    int inIndex = mDecoder.dequeueInputBuffer(10000); 
    if (inIndex >= 0) { 
     // Valid buffer returned 
     int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0); 
     if (sampleSize < 0) { 
      Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); 
      mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      isEOS = true; 
     } else { 
      mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); 
      mExtractor.advance(); 
     } 
    } 

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000); 
    if (outIndex >= 0) { 
     // Valid buffer returned 
     ByteBuffer buffer = outputBuffers[outIndex]; 
     JniGlue.decoutFrame(buffer, info.offset, info.size); 
     mDecoder.releaseOutputBuffer(outIndex, true); 
    } else { 
     // Some INFO_* value returned 
     switch (outIndex) { 
     case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 
      Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED"); 
      outputBuffers = mDecoder.getOutputBuffers(); 
      break; 
     case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 
      Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat()); 
      break; 
     case MediaCodec.INFO_TRY_AGAIN_LATER: 
      // Timeout - simply ignore 
      break; 
     default: 
      // Some other value, simply ignore 
      break; 
     } 
    } 

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
     Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM"); 
     isEOS = true; 
    } 
} 
+0

Vẫn không có giải pháp. Mọi đề xuất vẫn được hoan nghênh. Ngoài ra đề xuất cho các thử nghiệm để tăng sự hiểu biết. Bất cứ ai có giải mã phần cứng làm việc bằng cách sử dụng MediaCodec? Có lẽ trên một nền tảng khác? – Bram

+0

Bram, tôi đang cố gắng giải quyết chính xác cùng một vấn đề. Dường như sự chậm lại này không phải là về nhiều bản sao của bộ đệm được giải mã.Khi dữ liệu được giải mã có nghĩa là để trình bày lên một bề mặt riêng, có vẻ như có một số đường dẫn dữ liệu trực tiếp và nó sử dụng TILER (kết xuất lát gạch). Khi bạn cần truy cập khung YUV đầy đủ (ví dụ: bạn muốn truy cập bộ giải mã được giải mã) bộ giải mã cần thực hiện thêm một số tác vụ như hiển thị tất cả dữ liệu đó vào bộ nhớ đệm và sao chép nó làm chậm quá trình này. Tôi thực sự lãng phí một tuần trong cuộc đời tôi cố gắng khắc phục vấn đề, nhưng dường như không có gì để sửa chữa. – Pavel

+0

Hơn nữa, trong trường hợp của tôi, tôi đã có một 720p @ 30fps mà tôi đã không thể giải mã thời gian thực trong khi người chơi bản địa không có vấn đề trả tiền. – Pavel

Trả lời

3

Nếu bạn định cấu hình đầu ra Bề mặt, dữ liệu được giải mã được ghi vào bộ đệm đồ họa có thể được sử dụng làm kết cấu OpenGL ES (thông qua phần mở rộng "kết cấu bên ngoài"). Các bit khác nhau của phần cứng nhận được để bàn tay dữ liệu xung quanh trong một định dạng mà họ thích, và CPU không phải sao chép dữ liệu.

Nếu bạn không định cấu hình Bề mặt, đầu ra sẽ chuyển thành java.nio.ByteBuffer. Có ít nhất một bản sao đệm để lấy dữ liệu từ bộ đệm được cấp phát MediaCodec đến ByteByffer của bạn và có lẽ một bản sao khác để lấy lại dữ liệu vào mã JNI của bạn. Tôi hy vọng những gì bạn thấy là chi phí trên không thay vì chi phí giải mã phần mềm.

Bạn thể có thể cải thiện tình hình bằng cách gửi đầu ra vào một SurfaceTexture, xé thành một FBO hoặc pBuffer, và sau đó sử dụng glReadPixels để trích xuất các dữ liệu. Nếu bạn đọc một "trực tiếp" ByteBuffer hoặc gọi glReadPixels từ mã gốc, bạn sẽ giảm chi phí JNI của mình. Mặt trái của phương pháp này là dữ liệu của bạn sẽ nằm trong RGB chứ không phải là YCbCr. (OTOH, nếu các biến đổi mong muốn của bạn có thể được thể hiện trong bộ đổ bóng đoạn GLES 2.0, bạn có thể lấy GPU để thực hiện công việc thay vì CPU.)

Như đã nói trong một câu trả lời khác, bộ giải mã trên các thiết bị khác nhau xuất ra dữ liệu ByteBuffer ở các định dạng khác nhau, vì vậy việc diễn giải dữ liệu trong phần mềm có thể không khả thi nếu tính di động quan trọng đối với bạn.

Chỉnh sửa:Grafika hiện có ví dụ về cách sử dụng GPU để xử lý hình ảnh. Bạn có thể xem video giới thiệu here.

+0

Cảm ơn! Vì vậy, bạn nói với @fadden rằng sử dụng một bề mặt chúng tôi loại trừ lẫn nhau để truy cập ByteBuffer. Thiếu tài liệu trong [dequeueOutputBuffer] (http://developer.android.com/reference/android/media/MediaCodec.html#dequeueOutputBuffer%28android.media.MediaCodec.BufferInfo,%20long%29)?!?! Vâng, đối với tôi, tôi cần YCbCr trong mã nguồn gốc, vì vậy việc chuyển sang GLES không giúp được gì. Hơn nữa, bề mặt chỉ để thử nghiệm. Về sao chép dữ liệu? Điều đó sẽ mất 40ms (25 frm/s)? Trong mã nguồn gốc của tôi, tôi có thể làm điều đó trong 11 ms. Vì vậy, vẫn còn quá tải CPU. Đúng? Btw. tính di động không phải là mối quan tâm chính của tôi. – Bram

+1

Tài liệu 'MediaCodec' có thể tốt hơn. Thật khó để biết mọi lúc mọi nơi đang đi mà không thể nhìn thấy tất cả những gì thiết bị của bạn đang làm - tất cả đều khác một chút. Có thể định dạng "bản địa" được Surface sử dụng là điều gì đó điên rồ và chúng chuyển mã nó sang YUV đơn giản trên đường tới ByteBuffer. Đi từ 25fps đến 55fps là chênh lệch 40-18 = 22ms - hai bản sao đệm của bạn. Đôi khi trong đầu ra logcat, bạn có thể thấy nó mở (hoặc không) thiết bị giải mã phần cứng. Trong mọi trường hợp, tôi không thấy bất cứ điều gì sai trái với những gì bạn đang làm. – fadden

+0

Kiểm tra đầu ra logcat. Nó không hiển thị bất kỳ sự khác biệt nào khi vận hành với bề mặt so với không có bề mặt (trong khi thực sự một số thông tin được hiển thị khi khởi động và dừng bộ giải mã). Vì vậy, điều này xác nhận tuyên bố của bạn: ứng dụng của chúng tôi không bị giải mã trong phần mềm, thay vì từ trên cao. Vì vậy, điều này sẽ đóng chủ đề hiện tại. Chủ đề tiếp theo rõ ràng sẽ là: làm thế nào để giảm chi phí này? Nếu bản thân android có thể đưa hình ảnh vào màn hình với tải gần 0 cpu, làm thế nào tôi có thể tải chúng trong ứng dụng của tôi với tải gần 0 cpu? – Bram

0

tôi sử dụng api MediaCodec trên Nexus 4 và nhận được định dạng màu đầu ra của QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka. Tôi nghĩ định dạng này là một loại định dạng phần cứng và chỉ có thể được hiển thị bằng cách hiển thị phần cứng. Thật thú vị, tôi thấy rằng khi tôi sử dụng bề mặt rỗng và thực tế để cấu hình bề mặt cho MediaCodec, độ dài đệm đầu ra sẽ được thay đổi thành giá trị thực và 0 tương ứng. Tôi không biết tại sao. Tôi nghĩ bạn có thể thực hiện một số thử nghiệm trên các thiết bị khác nhau để có nhiều kết quả hơn. Về tăng tốc phần cứng, bạn có thể xem http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/

+0

Cảm ơn bạn đã gợi ý về các định dạng màu. Không có bề mặt, 'mDecoder.getOutputFormat(). GetInteger (" định dạng màu ")' của tôi là 19 (COLOR_FormatYUV420Planar trong [link] (http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html)). Với bề mặt, nó là 256. Không có ý tưởng gì có nghĩa là ... Hơn nữa với một bề mặt thực tế, info.size trở thành 0. Rõ ràng, tôi không nên cố gắng để đọc một bộ đệm với kích thước 0 trong JniGlue.decoutFrame() của tôi. Điều này có thể giải thích sự cố. Nhưng vẫn ... cung cấp một bề mặt chỉ là một giải pháp để có được giải mã phần cứng để chạy ... – Bram

+0

Với đầu ra cho một bề mặt, bạn không nhận được bất kỳ dữ liệu trong 'ByteBuffer'. Bạn vẫn nhận được một chỉ mục trở lại từ 'dequeueOutputBuffer()' để bạn có thể được thông báo rằng một khung có sẵn và chọn có hay không trả về nó với 'render' arg to' releaseOutputBuffer() '. Bạn không thể chạm vào các bit thực tế mà không hiển thị Surface. – fadden

+0

Cho dù tôi đặt decoder.configure (định dạng, null, null, 0); OR decoder.configure (định dạng, bề mặt, null, 0); Tôi nhận định dạng màu giống nhau - COLOR_QCOM_FormatYUV420SemiPlanar - Giá trị không đổi: 2141391872 (0x7fa30c00) cho Nexus 7 VÀ COLOR_TI_FormatYUV420PackedSemiPlanar - Giá trị không đổi: 2130706688 (0x7f000100) cho Galaxy Nexus. Tại sao? – Harkish

Các vấn đề liên quan