1

我正在使用 android MediaCodec 解码和编码视频文件。除了Pixel 3a device之外,解码和编码都可以正常使用以下代码。对于编码,编码器使用一组位图来创建视频文件。但只有在 Pixel 3A 设备上,编码位图才会失败并产生失真的视频文件。

设备详细信息: 名称:Pixel 3A,Android 版本:11

public class ImageProcessor implements Runnable {
    private static final String VIDEO = "video/";
    private static final String TAG = "VideoDecoder";
    private static final long DEFAULT_TIMEOUT_US = 0;[enter image description here][1]
    private final String inputFile;
    private final String outputFile;
    private MediaCodec mDecoder;

    private MediaExtractor mExtractor;
    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private int width;
    private int height;

    private MediaCodec mEncoder;
    private MediaMuxer mediaMuxer;
    private int mTrackIndex;
    private ScriptC_rotators rotateScript;
    private int newWidth = 0, newHeight = 0;

    private int preRotateHeight;
    private int preRotateWidth;
    private Allocation fromRotateAllocation;
    private Allocation toRotateAllocation;
    private int frameIndex;
    private int deviceOrientation;
    private int sensorOrientation;
    private final Handler handler;

    boolean sawOutputEOS = false;
    boolean sawInputEOS = false;

    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private FrameObject defaultObject;
    private int faceBlurCount;
    private long startTime;
    private float frameRate;
    private int generateIndex;

    public ImageProcessor1(Handler handler, String inputFile, String outputFile) {
        this.inputFile = inputFile;
        this.outputFile = outputFile;
        this.handler = handler;
    }

    public void setDeviceOrientation(int deviceOrientation) {
        this.deviceOrientation = deviceOrientation;
    }

    public void setSensorOrientation(int sensorOrientation) {
        this.sensorOrientation = sensorOrientation;
    }

    public void setDefaultObject(FrameObject frameObject) {
        this.defaultObject = frameObject;
    }

    private void init() {
        try {
            mExtractor = new MediaExtractor();
            mExtractor.setDataSource(inputFile);
            
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(inputFile);

            FFmpegMediaMetadataRetriever metadataRetriever = new FFmpegMediaMetadataRetriever();
            metadataRetriever.setDataSource(inputFile);

            rs = RenderScript.create(Globals.getAppContext());
            yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
            rotateScript = new ScriptC_rotators(rs);

            for (int i = 0; i < mExtractor.getTrackCount(); i++) {
                MediaFormat format = mExtractor.getTrackFormat(i);

                String mimeType = format.getString(MediaFormat.KEY_MIME);

                width = format.getInteger(MediaFormat.KEY_WIDTH);
                height = format.getInteger(MediaFormat.KEY_HEIGHT);

                frameRate = Float.parseFloat(metadataRetriever.extractMetadata(
                        FFmpegMediaMetadataRetriever.METADATA_KEY_FRAMERATE));

                int bitRate = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
                if (mimeType != null && mimeType.startsWith(VIDEO)) {
                    mExtractor.selectTrack(i);
                    mDecoder = MediaCodec.createDecoderByType(mimeType);
                    mDecoder.configure(format, null, null, 0 /* Decoder */);
                    mDecoder.start();

                    MediaCodecInfo mediaCodecInfo = selectCodec(mimeType);
                    if (mediaCodecInfo == null) {
                        throw new RuntimeException("Failed to initialise codec");
                    }

                    switch (deviceOrientation) {
                        case Surface.ROTATION_0:
                        case Surface.ROTATION_180:
                            newWidth = height;
                            newHeight = width;
                            break;

                        case Surface.ROTATION_90:
                        case Surface.ROTATION_270:
                            newWidth = width;
                            newHeight = height;
                            break;
                    }
                    MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, newWidth, newHeight);
                    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
                    mediaFormat.setFloat(MediaFormat.KEY_FRAME_RATE, frameRate);
                    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
                    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
                    mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                    mEncoder.start();

                    mediaMuxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to initialise codec");
        }
    }


    /**
     * Returns the first codec capable of encoding the specified MIME type, or null if no
     * match was found.
     */
    private MediaCodecInfo selectCodec(String mimeType) throws IOException {
        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
        MediaCodecInfo[] codecInfos = list.getCodecInfos();
        for (MediaCodecInfo info : codecInfos) {
            if (info.isEncoder()) {
                mEncoder = MediaCodec.createByCodecName(info.getName());
                String[] types = info.getSupportedTypes();
                for (String type : types) {
                    if (type.equalsIgnoreCase(mimeType)) {
                        return info;
                    }
                }
            }
        }
        return null;
    }

    public void startProcessing() {
        init();

        MediaCodec.BufferInfo decoderBufferInfo = new MediaCodec.BufferInfo();
        MediaCodec.BufferInfo encoderBufferInfo = new MediaCodec.BufferInfo();
        startTime = System.currentTimeMillis();
        while (!sawOutputEOS) {
            Log.d(TAG, "startProcessing: " + frameIndex);
            if (!sawInputEOS && mDecoder != null) {
                int inputBufferId = mDecoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
                if (inputBufferId >= 0) {
                    ByteBuffer inputBuffer = mDecoder.getInputBuffer(inputBufferId);
                    int sampleSize = mExtractor.readSampleData(inputBuffer, 0);
                    if (sampleSize < 0) {
                        mDecoder.queueInputBuffer(inputBufferId, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        sawInputEOS = true;
                    } else {
                        if (mExtractor != null) {
                            long presentationTimeUs = mExtractor.getSampleTime();
                            mDecoder.queueInputBuffer(inputBufferId, 0, sampleSize, presentationTimeUs, 0);
                            mExtractor.advance();
                        }
                    }
                }
            }

            int outputBufferId = mDecoder.dequeueOutputBuffer(decoderBufferInfo, DEFAULT_TIMEOUT_US);
            if (outputBufferId >= 0) {
                if ((decoderBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    sawOutputEOS = true;
                    Log.d(TAG, "endProcessing: " + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime));
                }

                boolean doRender = (decoderBufferInfo.size != 0);
                if (doRender && mDecoder != null) {
                    Image image = mDecoder.getOutputImage(outputBufferId);
                    if (image != null) {
                        try {
                            frameIndex++;
                            byte[] frameData = quarterNV21(convertYUV420888ToNV21(image), image.getWidth(), image.getHeight());
                            byte[] data = getDataFromImage(image);

                            Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(data.length);
                            Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

                            Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
                            Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);

                            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

                            in.copyFromUnchecked(data);

                            yuvToRgbIntrinsic.setInput(in);
                            yuvToRgbIntrinsic.forEach(out);
                            out.copyTo(bitmap);
                            image.close();
                            encodeBitmaps(bitmap, encoderBufferInfo);
                        } catch (Exception e) {
                            Log.d(TAG, "startProcessing: " + e.getMessage());
                        }
                    }
                    if (mDecoder != null) {
                        mDecoder.releaseOutputBuffer(outputBufferId, false);
                    }
                }
            }
        }
    }

    private long computePresentationTime(int frameIndex) {
        return 132 + frameIndex * 1000000 / (int)frameRate;
    }

    private byte[] convertYUV420888ToNV21(Image image) {
        byte[] data;
        ByteBuffer buffer0 = image.getPlanes()[0].getBuffer();
        ByteBuffer buffer2 = image.getPlanes()[2].getBuffer();
        int buffer0_size = buffer0.remaining();
        int buffer2_size = buffer2.remaining();
        data = new byte[buffer0_size + buffer2_size];
        buffer0.get(data, 0, buffer0_size);
        buffer2.get(data, buffer0_size, buffer2_size);
        return data;
    }

    private byte[] quarterNV21(byte[] data, int iWidth, int iHeight) {
        byte[] yuv = new byte[iWidth * iHeight * 3 / 2];
        // halve yuma
        int i = 0;
        for (int y = 0; y < iHeight; y++) {
            for (int x = 0; x < iWidth; x++) {
                yuv[i] = data[y * iWidth + x];
                i++;
            }
        }
        return yuv;
    }

    private void release() {
        try {
            if (mExtractor != null) {
                mExtractor.release();
                mExtractor = null;
            }
            if (mDecoder != null) {
                mDecoder.stop();
                mDecoder.release();
                mDecoder = null;
            }

            if (mEncoder != null) {
                mEncoder.stop();
                mEncoder.release();
                mEncoder = null;
            }

            if (mediaMuxer != null) {
                mediaMuxer.stop();
                mediaMuxer.release();
                mediaMuxer = null;
            }

        } catch (Exception e) {
            Log.d(TAG, "imageprocessor release: " + e.fillInStackTrace());
        }
        Message message = handler.obtainMessage();
        Bundle bundle = new Bundle();
        bundle.putString(FrameUtil.COMPUTATION_SUCCESS_KEY, this.outputFile);
        bundle.putInt(FrameUtil.FACE_BLUR_COUNT, faceBlurCount);
        message.setData(bundle);
        handler.sendMessage(message);
    }

    // encode the bitmap to a new video file
    private void encodeBitmaps(Bitmap bitmap, MediaCodec.BufferInfo encoderBufferInfo) {
        Bitmap rotatedBitmap = null;
        switch (deviceOrientation) {
            case Surface.ROTATION_0:
                if (sensorOrientation == SENSOR_ORIENTATION_DEFAULT_DEGREES) {
                    rotatedBitmap = rotateBitmap(bitmap, 270);
                } else {
                    rotatedBitmap = rotateBitmap(bitmap, 90);
                }
                break;

            case Surface.ROTATION_90:
                Bitmap newBitmap = rotateBitmap(bitmap, 90);
                bitmap.recycle();
                rotatedBitmap = rotateBitmap(newBitmap, 90);
                break;

            default:
                rotatedBitmap = bitmap;
        }

        byte[] bytes = getNV21(rotatedBitmap.getWidth(), rotatedBitmap.getHeight(), rotatedBitmap);
        int inputBufIndex = mEncoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
        long ptsUsec = computePresentationTime(generateIndex);
        if (inputBufIndex >= 0) {
            ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufIndex);
            if (inputBuffer != null) {
                inputBuffer.clear();
                inputBuffer.put(bytes);
                mEncoder.queueInputBuffer(inputBufIndex, 0, bytes.length,
                        ptsUsec, 0);
                generateIndex++;
            }
        }
        int encoderStatus = mEncoder.dequeueOutputBuffer(encoderBufferInfo, DEFAULT_TIMEOUT_US);
        if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat newFormat = mEncoder.getOutputFormat();
            mTrackIndex = mediaMuxer.addTrack(newFormat);
            mediaMuxer.start();
        } else if (encoderBufferInfo.size != 0) {
            ByteBuffer outputBuffer = mEncoder.getOutputBuffer(encoderStatus);
            if (outputBuffer != null) {
                outputBuffer.position(encoderBufferInfo.offset);
                outputBuffer.limit(encoderBufferInfo.offset + encoderBufferInfo.size);
                mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, encoderBufferInfo);
                mEncoder.releaseOutputBuffer(encoderStatus, false);
            }
            if ((encoderBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                mEncoder.signalEndOfInputStream();
            }
        }
    }
    
    private Allocation getFromRotateAllocation(Bitmap bitmap) {
        int targetHeight = bitmap.getWidth();
        int targetWidth = bitmap.getHeight();
        if (targetHeight != preRotateHeight || targetWidth != preRotateWidth) {
            preRotateHeight = targetHeight;
            preRotateWidth = targetWidth;
            fromRotateAllocation = Allocation.createFromBitmap(rs, bitmap,
                    Allocation.MipmapControl.MIPMAP_NONE,
                    Allocation.USAGE_SCRIPT);
        }
        return fromRotateAllocation;
    }

    private Allocation getToRotateAllocation(Bitmap bitmap) {
        int targetHeight = bitmap.getWidth();
        int targetWidth = bitmap.getHeight();
        if (targetHeight != preRotateHeight || targetWidth != preRotateWidth) {
            toRotateAllocation = Allocation.createFromBitmap(rs, bitmap,
                    Allocation.MipmapControl.MIPMAP_NONE,
                    Allocation.USAGE_SCRIPT);
        }
        return toRotateAllocation;
    }


    private Bitmap rotateBitmap(Bitmap bitmap, int angle) {
        Bitmap.Config config = bitmap.getConfig();
        int targetHeight = bitmap.getWidth();
        int targetWidth = bitmap.getHeight();

        rotateScript.set_inWidth(bitmap.getWidth());
        rotateScript.set_inHeight(bitmap.getHeight());

        Allocation sourceAllocation = getFromRotateAllocation(bitmap);
        sourceAllocation.copyFrom(bitmap);
        rotateScript.set_inImage(sourceAllocation);

        Bitmap target = Bitmap.createBitmap(targetWidth, targetHeight, config);
        final Allocation targetAllocation = getToRotateAllocation(target);
        if (angle == 90) {
            rotateScript.forEach_rotate_90_clockwise(targetAllocation, targetAllocation);
        } else {
            rotateScript.forEach_rotate_270_clockwise(targetAllocation, targetAllocation);
        }

        targetAllocation.copyTo(target);

        return target;
    }

    private byte[] getNV21(int inputWidth, int inputHeight, Bitmap bitmap) {
        int[] argb = new int[inputWidth * inputHeight];
        bitmap.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
        byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];
        encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
        bitmap.recycle();
        return yuv;
    }

    private void encodeYUV420SP(byte[] yuv420sp, int[] rgb, int width, int height) {

        final int frameSize = width * height;

        int yIndex = 0;
        int uvIndex = frameSize;

        int R, G, B, Y, U, V;
        int index = 0;
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {

                //a = (aRGB[index] & 0xff000000) >> 24; //not using it right now
                R = (rgb[index] & 0xff0000) >> 16;
                G = (rgb[index] & 0xff00) >> 8;
                B = (rgb[index] & 0xff);


                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

                yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : (Math.min(Y, 255)));
                if (j % 2 == 0 && index % 2 == 0) {
                    yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : (Math.min(U, 255)));
                    yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : (Math.min(V, 255)));

                }
                index++;
            }
        }
    }

    private static byte[] getDataFromImage(Image image) {
        Rect crop = image.getCropRect();
        int format = image.getFormat();
        int width = crop.width();
        int height = crop.height();
        Image.Plane[] planes = image.getPlanes();
        byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
        byte[] rowData = new byte[planes[0].getRowStride()];

        int channelOffset = 0;
        int outputStride = 1;
        for (int i = 0; i < planes.length; i++) {
            switch (i) {
                case 0:
                    channelOffset = 0;
                    outputStride = 1;
                    break;
                case 1:
                    channelOffset = width * height + 1;
                    outputStride = 2;
                    break;
                case 2:
                    channelOffset = width * height;
                    outputStride = 2;
                    break;
            }

            ByteBuffer buffer = planes[i].getBuffer();
            int rowStride = planes[i].getRowStride();
            int pixelStride = planes[i].getPixelStride();

            int shift = (i == 0) ? 0 : 1;
            int w = width >> shift;
            int h = height >> shift;
            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
            for (int row = 0; row < h; row++) {
                int length;
                if (pixelStride == 1 && outputStride == 1) {
                    length = w;
                    buffer.get(data, channelOffset, length);
                    channelOffset += length;
                } else {
                    length = (w - 1) * pixelStride + 1;
                    buffer.get(rowData, 0, length);
                    for (int col = 0; col < w; col++) {
                        data[channelOffset] = rowData[col * pixelStride];
                        channelOffset += outputStride;
                    }
                }
                if (row < h - 1) {
                    buffer.position(buffer.position() + rowStride - length);
                }
            }
        }
        return data;
    }
    
    @Override
    public void run() {
        try {
            startProcessing();
        } catch (Exception ex) {
            Log.d(TAG, "run: " + ex.getCause());
        } finally {
            release();
        }
    }

    public void stopProcessing() {
        sawOutputEOS = true;
    }
}

请看一下代码并告诉我我做错了什么。

扭曲的视频帧

4

0 回答 0