来自相机的 MediaCodec 视频流方向和颜色错误

2024-05-18

我正在尝试流式传输视频捕获直接从相机适用于 Android 设备。到目前为止,我已经能够从 Android 相机捕获每一帧预览帧(byte[] data, Camera camera) 函数,对数据进行编码,然后成功解码数据并显示到表面。我用的是安卓的媒体编解码器用于编码和解码。但是颜色和方向视频的内容不正确[90度旋转]。经过一段时间的搜索后,我发现了这个 YV12toYUV420PackedSemiPlanar 函数 - 如果我在原始相机数据上使用此函数,然后将其传递给编码器,则颜色显示正确,但它是仍然旋转90度.

public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final int width, final int height) {

    final int frameSize = width * height;
    final int qFrameSize = frameSize/4;
    byte[] output = new byte[input.length];


    System.arraycopy(input, 0, output, 0, frameSize);
    for (int i = 0; i < (qFrameSize); i++) 
    {
        byte b = (input[frameSize + qFrameSize + i - 32 - 320]);
        output[frameSize + i*2] =   b;
        output[frameSize + i*2 + 1] = (input[frameSize + i - 32 - 320]);            
    }
    System.arraycopy(input, 0, output, 0, frameSize); // Y
    for (int i = 0; i < qFrameSize; i++) {
        output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
        output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
    }
    return output;
}

然后我用了这个函数,rotateYUV420Degree90after调用 YV12toYUV420PackedSemiPlanar 函数。看来方向和颜色都可以但输出的视频非常扭曲的.

private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) 
{
    byte [] yuv = new byte[imageWidth*imageHeight*3/2];
    // Rotate the Y luma
    int i = 0;
    for(int x = 0;x < imageWidth;x++)
    {
        for(int y = imageHeight-1;y >= 0;y--)                               
        {
            yuv[i] = data[y*imageWidth+x];
            i++;
        }
    }
    // Rotate the U and V color components 
    i = imageWidth*imageHeight*3/2-1;
    for(int x = imageWidth-1;x > 0;x=x-2)
    {
        for(int y = 0;y < imageHeight/2;y++)                                
        {
            yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+x];
            i--;
            yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
            i--;
        }
    }
    return yuv;
}

由于我对颜色规格和相机数据知之甚少,我无法理解我做错了什么。这是我的总代码 - 请看一下并帮助我找到我的错误。

提前致谢。

public class MainActivity extends Activity implements SurfaceHolder.Callback  {

    Camera mCamera;
    FileOutputStream fos;
    File mVideoFile;
    MediaCodec mMediaCodec;
    ByteBuffer[] inputBuffers;
    ByteBuffer[] outputBuffers;
    MySurfaceView cameraSurfaceView ;
    SurfaceView decodedSurfaceView ;
    LinearLayout ll;
    RelativeLayout rl;
    Button btn;
    boolean mPreviewRunning = false;
    boolean firstTime = true;
    boolean isRunning = false;
    public static final String ENCODING = "h264";  

    private PlayerThread mPlayer = null;
    Handler handler = null;
    public static byte[] SPS = null;
    public static byte[] PPS = null;
    public static int frameID = 0;
    BlockingQueue<Frame> queue = new ArrayBlockingQueue<Frame>(100);

    private static class Frame
    {
        public int id;
        public byte[] frameData;

        public Frame(int id)
        {
            this.id = id;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ll = new LinearLayout(getApplicationContext());
        ll.setOrientation(LinearLayout.VERTICAL);

        cameraSurfaceView = new MySurfaceView(getApplicationContext());
        if(ENCODING.equalsIgnoreCase("h264"))
        {
            cameraSurfaceView.setLayoutParams(new android.widget.FrameLayout.LayoutParams(320, 240));
        }
        else if(ENCODING.equalsIgnoreCase("h263"))
        {
            cameraSurfaceView.setLayoutParams(new android.widget.FrameLayout.LayoutParams(352, 288));
        }
        ll.addView(cameraSurfaceView);

        initCodec();
        setContentView(ll);

    }

    @Override
    protected void onPause() {

        super.onPause();
        mPreviewRunning = false;

        if(cameraSurfaceView !=null && cameraSurfaceView.isEnabled())
            cameraSurfaceView.setEnabled(false);
        cameraSurfaceView = null;

        if(mCamera != null)
        {
            mCamera.stopPreview();
            mCamera.release();
        }

        System.exit(0);

        mMediaCodec.stop();
        mMediaCodec.release();
        mMediaCodec = null;

    };


    private void initCodec() {

        MediaFormat mediaFormat = null;

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

        if(ENCODING.equalsIgnoreCase("h264"))
        {
            mMediaCodec = MediaCodec.createEncoderByType("video/avc");
            mediaFormat = MediaFormat.createVideoFormat("video/avc",
                    320,
                    240);
        }
        else if(ENCODING.equalsIgnoreCase("h263"))
        {
            mMediaCodec = MediaCodec.createEncoderByType("video/3gpp");
            mediaFormat = MediaFormat.createVideoFormat("video/3gpp",
                    352,
                    288);
        }

        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);

        try
        {
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);

            mMediaCodec.configure(mediaFormat,
                    null,
                    null,
                    MediaCodec.CONFIGURE_FLAG_ENCODE);
            frameID = 0;
            mMediaCodec.start();
        }
        catch(Exception e)
        {
            Toast.makeText(getApplicationContext(), "mediaformat error", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }

    }

    /**========================================================================*/
    /** This function gets the starting index of the first appearance of match array in source array. The function will search in source array from startIndex position.*/
    public static int find(byte[] source, byte[] match, int startIndex) 
    {  
        if(source == null || match == null)
        {
            Log.d("EncodeDecode", "ERROR in find : null");
            return -1;
        }
        if(source.length == 0 || match.length == 0)
        {
            Log.d("EncodeDecode", "ERROR in find : length 0");
            return -1;
        }

        int ret = -1;  
        int spos = startIndex;  
        int mpos = 0;  
        byte m = match[mpos];  
        for( ; spos < source.length; spos++ ) 
        {  
            if(m == source[spos]) 
            {  
                // starting match  
                if(mpos == 0)  
                    ret = spos;  
                // finishing match  
                else if(mpos == match.length - 1)  
                    return ret;  

                mpos++;  
                m = match[mpos];  
            }  
            else 
            {  
                ret = -1;  
                mpos = 0;  
                m = match[mpos];  
            }  
        }  
        return ret;  
    }


    /**========================================================================*/
    /** For H264 encoding, this function will retrieve SPS & PPS from the given data and will insert into SPS & PPS global arrays. */
    public static void getSPS_PPS(byte[] data, int startingIndex)
    {
        byte[] spsHeader = {0x00, 0x00, 0x00, 0x01, 0x67};
        byte[] ppsHeader = {0x00, 0x00, 0x00, 0x01, 0x68};
        byte[] frameHeader = {0x00, 0x00, 0x00, 0x01};

        int spsStartingIndex = -1;
        int nextFrameStartingIndex = -1;
        int ppsStartingIndex = -1;

        spsStartingIndex = find(data, spsHeader, startingIndex);
        Log.d("EncodeDecode", "spsStartingIndex: " + spsStartingIndex);
        if(spsStartingIndex >= 0)
        {
            nextFrameStartingIndex = find(data, frameHeader, spsStartingIndex+1);
            int spsLength = 0;
            if(nextFrameStartingIndex>=0)
                spsLength = nextFrameStartingIndex - spsStartingIndex;
            else
                spsLength = data.length - spsStartingIndex;
            if(spsLength > 0)
            {
                SPS = new byte[spsLength];
                System.arraycopy(data, spsStartingIndex, SPS, 0, spsLength);
            }
        }

        ppsStartingIndex = find(data, ppsHeader, startingIndex);
        Log.d("EncodeDecode", "ppsStartingIndex: " + ppsStartingIndex);
        if(ppsStartingIndex >= 0)
        {
            nextFrameStartingIndex = find(data, frameHeader, ppsStartingIndex+1);
            int ppsLength = 0;
            if(nextFrameStartingIndex>=0)
                ppsLength = nextFrameStartingIndex - ppsStartingIndex;
            else
                ppsLength = data.length - ppsStartingIndex;
            if(ppsLength > 0)
            {
                PPS = new byte[ppsLength];
                System.arraycopy(data, ppsStartingIndex, PPS, 0, ppsLength);
            }
        }
    }


    /**========================================================================*/
    /** Prints the byte array in hex */
    private void printByteArray(byte[] array)
    {
        StringBuilder sb1 = new StringBuilder();
        for (byte b : array) 
        {
            sb1.append(String.format("%02X ", b));
        }
        Log.d("EncodeDecode", sb1.toString());
    }

    public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final int width, final int height) {
        /* 
         * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12
         * We convert by putting the corresponding U and V bytes together (interleaved).
         */
        final int frameSize = width * height;
        final int qFrameSize = frameSize/4;
        byte[] output = new byte[input.length];


        System.arraycopy(input, 0, output, 0, frameSize);
        for (int i = 0; i < (qFrameSize); i++) 
        {
            byte b = (input[frameSize + qFrameSize + i - 32 - 320]);
            output[frameSize + i*2] =   b;
            output[frameSize + i*2 + 1] = (input[frameSize + i - 32 - 320]);            
        }



        System.arraycopy(input, 0, output, 0, frameSize); // Y

        for (int i = 0; i < qFrameSize; i++) {
            output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
            output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
        }
        return output;
    }

    private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) 
    {
        byte [] yuv = new byte[imageWidth*imageHeight*3/2];
        // Rotate the Y luma
        int i = 0;
        for(int x = 0;x < imageWidth;x++)
        {
            for(int y = imageHeight-1;y >= 0;y--)                               
            {
                yuv[i] = data[y*imageWidth+x];
                i++;
            }
        }
        // Rotate the U and V color components 
        i = imageWidth*imageHeight*3/2-1;
        for(int x = imageWidth-1;x > 0;x=x-2)
        {
            for(int y = 0;y < imageHeight/2;y++)                                
            {
                yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+x];
                i--;
                yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
                i--;
            }
        }
        return yuv;
    }

    /**========================================================================*/
    /** When camera receives a frame this function is called with the frame data as its parameter. It encodes the given data and then stores in frameQueue. */
    private void encode(byte[] data)
    {
        Log.d("EncodeDecode", "ENCODE FUNCTION CALLED");
        inputBuffers = mMediaCodec.getInputBuffers();
        outputBuffers = mMediaCodec.getOutputBuffers();

        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
        if (inputBufferIndex >= 0)
        {
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();

            int size = inputBuffer.limit();
            //inputBuffer.put(data);

            // color right, but rotated
            byte[] output = YV12toYUV420PackedSemiPlanar(data,320,240);
            inputBuffer.put(output);

            // color almost right, orientation ok but distorted 
            /*byte[] output = YV12toYUV420PackedSemiPlanar(data,320,240);
            output = rotateYUV420Degree90(output,320,240);
            inputBuffer.put(output);*/

            mMediaCodec.queueInputBuffer(inputBufferIndex, 0 /* offset */, size, 0 /* timeUs */, 0);
            Log.d("EncodeDecode", "InputBuffer queued");
        }
        else
        {
            Log.d("EncodeDecode", "inputBufferIndex < 0, returning null");
            return ;
        }

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
        Log.d("EncodeDecode", "outputBufferIndex = " + outputBufferIndex);
        do
        {
            if (outputBufferIndex >= 0)
            {
                Frame frame = new Frame(frameID);
                ByteBuffer outBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                byte idrFrameType = 0x65;
                int dataLength = 0;

                outBuffer.get(outData);

                // If SPS & PPS is not ready then 
                if(ENCODING.equalsIgnoreCase("h264") && ( (SPS == null || SPS.length ==0) || (PPS == null || PPS.length == 0) ) )
                    getSPS_PPS(outData, 0);

                dataLength = outData.length;

                // If the frame is an IDR Frame then adding SPS & PPS in front of the actual frame data 
                if(ENCODING.equalsIgnoreCase("h264") && outData[4] == idrFrameType)
                {
                    int totalDataLength = dataLength + SPS.length + PPS.length;

                    frame.frameData = new byte[totalDataLength];

                    System.arraycopy(SPS, 0, frame.frameData, 0, SPS.length);
                    System.arraycopy(PPS, 0, frame.frameData, SPS.length, PPS.length);
                    System.arraycopy(outData, 0 , frame.frameData, SPS.length+PPS.length, dataLength);
                }
                else
                {
                    frame.frameData = new byte[dataLength];
                    System.arraycopy(outData, 0 , frame.frameData, 0, dataLength);
                }

                // for testing
                Log.d("EncodeDecode" , "Frame no :: " + frameID + " :: frameSize:: " + frame.frameData.length + " :: ");
                printByteArray(frame.frameData);

                // if encoding type is h264 and sps & pps is ready then, enqueueing the frame in the queue
                // if encoding type is h263 then, enqueueing the frame in the queue
                if( (ENCODING.equalsIgnoreCase("h264") && SPS != null && PPS != null && SPS.length != 0 && PPS.length != 0) || ENCODING.equalsIgnoreCase("h263") )
                {
                    Log.d("EncodeDecode", "enqueueing frame no: " + (frameID));

                    try
                    {
                        queue.put(frame);
                    }
                    catch(InterruptedException e)
                    {
                        Log.e("EncodeDecode", "interrupted while waiting");
                        e.printStackTrace();
                    }
                    catch(NullPointerException e)
                    {
                        Log.e("EncodeDecode", "frame is null");
                        e.printStackTrace();
                    }
                    catch(IllegalArgumentException e)
                    {
                        Log.e("EncodeDecode", "problem inserting in the queue");
                        e.printStackTrace();
                    }

                    Log.d("EncodeDecode", "frame enqueued. queue size now: " + queue.size());

                    if(firstTime)
                    {
                        Log.d("EncodeDecode", "adding a surface to layout for decoder");
                        SurfaceView sv = new SurfaceView(getApplicationContext());
                        handler = new Handler();
                        sv.getHolder().addCallback(MainActivity.this);
                        sv.setLayoutParams(new android.widget.FrameLayout.LayoutParams(320, 240));
                        ll.addView(sv,1);
                        MainActivity.this.setContentView(ll);
                        firstTime = false;
                    }
                }

                frameID++;
                mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);

            }
            else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
            {
                outputBuffers = mMediaCodec.getOutputBuffers();
                Log.e("EncodeDecode","output buffer of encoder : info changed");
            }
            else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
            {
                Log.e("EncodeDecode","output buffer of encoder : format changed");
            }
            else
            {
                Log.e("EncodeDecode", "unknown value of outputBufferIndex : " + outputBufferIndex);
                //printByteArray(data);
            }
        } while (outputBufferIndex >= 0);
    }

    private class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback  
    {
        SurfaceHolder holder;
        public MySurfaceView(Context context) {  
            super(context); 
            holder = this.getHolder();
            holder.addCallback(this); 
        }

        public MySurfaceView(Context context, AttributeSet attrs) {  
            super(context,attrs); 
            holder = this.getHolder();
            holder.addCallback(this); 
        }

        public void surfaceCreated(SurfaceHolder holder) {  
            try
            {
                try
                {
                    if(mCamera == null)
                        mCamera = Camera.open();
                    mCamera.setDisplayOrientation(90);
                    Log.d("EncodeDecode","Camera opened");
                }
                catch (Exception e)
                {
                    Log.d("EncodeDecode","Camera open failed");
                    e.printStackTrace();
                }

                Camera.Parameters p = mCamera.getParameters();

                if(ENCODING.equalsIgnoreCase("h264"))
                    p.setPreviewSize(320, 240);
                else if(ENCODING.equalsIgnoreCase("h263"))
                    p.setPreviewSize(352, 288);

                mCamera.setParameters(p);
                mCamera.setPreviewDisplay(holder);

                mCamera.setPreviewCallback(new PreviewCallback()
                {
                    @Override
                    public void onPreviewFrame(byte[] data, Camera camera)
                    { 
                        Log.d("EncodeDecode", "onPreviewFrame, calling encode function");
                        encode(data);
                    }
                });
                mCamera.startPreview();
                mPreviewRunning = true;
            } 
            catch (IOException e) 
            {
                Log.e("EncodeDecode","surfaceCreated():: in setPreviewDisplay(holder) function");
                e.printStackTrace();
            }
            catch (NullPointerException e)
            {
                Log.e("EncodeDecode","surfaceCreated Nullpointer");
                e.printStackTrace();
            }
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
        {  
            if (mPreviewRunning) 
            {
                mCamera.stopPreview();
                Log.e("EncodeDecode","preview stopped");
            }
            try 
            {
                if(mCamera == null)
                {
                    mCamera = Camera.open();
                    mCamera.setDisplayOrientation(90);
                }

                Camera.Parameters p = mCamera.getParameters();
                if(ENCODING.equalsIgnoreCase("h264"))
                    p.setPreviewSize(320, 240);
                else if(ENCODING.equalsIgnoreCase("h263"))
                    p.setPreviewSize(352, 288);

                p.setPreviewFormat(ImageFormat.YV12);
                mCamera.setParameters(p);
                mCamera.setPreviewDisplay(holder);
                mCamera.unlock();
                mCamera.reconnect();
                mCamera.setPreviewCallback(new PreviewCallback()
                {
                    @Override
                    public void onPreviewFrame(byte[] data, Camera camera)
                    {
                        Log.d("EncodeDecode", "onPreviewFrame, calling encode function");
                        encode(data);
                    }
                });
                Log.d("EncodeDecode", "previewCallBack set");
                mCamera.startPreview();
                mPreviewRunning = true;
            }
            catch (Exception e)
            {
                Log.e("EncodeDecode","surface changed:set preview display failed");
                e.printStackTrace();
            }

        }

        public void surfaceDestroyed(SurfaceHolder holder) 
        {

        }  
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) 
    {
        Log.d("EncodeDecode", "mainActivity surfaceCreated");
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
    {
        Log.d("EncodeDecode", "mainActivity surfaceChanged.");
        if (mPlayer == null) 
        {
            mPlayer = new PlayerThread(holder.getSurface());
            mPlayer.start();
            Log.d("EncodeDecode", "PlayerThread started");
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
    {
        if (mPlayer != null) 
        {
            mPlayer.interrupt();    
        }
    }

    private class PlayerThread extends Thread 
    {
        //private MediaExtractor extractor;
        private MediaCodec decoder;
        private Surface surface;

        public PlayerThread(Surface surface) 
        {
            this.surface = surface;
        }

        @Override
        public void run() 
        {
            while(SPS == null || PPS == null || SPS.length == 0 || PPS.length == 0)
            {
                try 
                {
                    Log.d("EncodeDecode", "DECODER_THREAD:: sps,pps not ready yet");
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
            }

            Log.d("EncodeDecode", "DECODER_THREAD:: sps,pps READY");

            if(ENCODING.equalsIgnoreCase("h264"))
            {
                decoder = MediaCodec.createDecoderByType("video/avc");
                MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
                mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(SPS));
                mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(PPS));
                decoder.configure(mediaFormat, surface /* surface */, null /* crypto */, 0 /* flags */);
            }
            else if(ENCODING.equalsIgnoreCase("h263"))
            { 
                decoder = MediaCodec.createDecoderByType("video/3gpp");
                MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/3gpp", 352, 288);
                decoder.configure(mediaFormat, surface /* surface */, null /* crypto */, 0 /* flags */);
            }

            if (decoder == null) 
            {
                Log.e("DecodeActivity", "DECODER_THREAD:: Can't find video info!");
                return;
            }

            decoder.start();
            Log.d("EncodeDecode", "DECODER_THREAD:: decoder.start() called");

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


            int i = 0;
            while(!Thread.interrupted())
            {
                Frame currentFrame = null;
                try 
                {
                    Log.d("EncodeDecode", "DECODER_THREAD:: calling queue.take(), if there is no frame in the queue it will wait");
                    currentFrame = queue.take();
                } 
                catch (InterruptedException e) 
                {
                    Log.e("EncodeDecode","DECODER_THREAD:: interrupted while PlayerThread was waiting for the next frame");
                    e.printStackTrace();
                }

                if(currentFrame == null)
                    Log.e("EncodeDecode","DECODER_THREAD:: null frame dequeued");
                else
                    Log.d("EncodeDecode","DECODER_THREAD:: " + currentFrame.id + " no frame dequeued");

                if(currentFrame != null && currentFrame.frameData != null && currentFrame.frameData.length != 0)
                {
                    Log.d("EncodeDecode", "DECODER_THREAD:: decoding frame no: " + i + " , dataLength = " + currentFrame.frameData.length);

                    int inIndex = 0; 
                    while ((inIndex = decoder.dequeueInputBuffer(1)) < 0)
                        ;

                    if (inIndex >= 0) 
                    {
                        Log.d("EncodeDecode", "DECODER_THREAD:: sample size: " + currentFrame.frameData.length);

                        ByteBuffer buffer = inputBuffers[inIndex];
                        buffer.clear();
                        buffer.put(currentFrame.frameData);
                        decoder.queueInputBuffer(inIndex, 0, currentFrame.frameData.length, 0, 0);

                        BufferInfo info = new BufferInfo();
                        int outIndex = decoder.dequeueOutputBuffer(info, 100000);

                        switch (outIndex) 
                        {
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            Log.e("EncodeDecode", "DECODER_THREAD:: INFO_OUTPUT_BUFFERS_CHANGED");
                            outputBuffers = decoder.getOutputBuffers();
                            break;
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                            Log.e("EncodeDecode", "DECODER_THREAD:: New format " + decoder.getOutputFormat());

                            break;
                        case MediaCodec.INFO_TRY_AGAIN_LATER:
                            Log.e("EncodeDecode", "DECODER_THREAD:: dequeueOutputBuffer timed out!");
                            break;
                        default:
                            Log.d("EncodeDecode", "DECODER_THREAD:: decoded SUCCESSFULLY!!!");
                            ByteBuffer outbuffer = outputBuffers[outIndex];
                            decoder.releaseOutputBuffer(outIndex, true);
                            break;
                        }
                        i++;
                    }
                }
            }

            decoder.stop();
            decoder.release();

        }
    }
}

当我制作一个可以通过 RTMP 实时广播相机帧的应用程序时,我在纵向模式下遇到同样的问题,但我可以通过使用 TextureView 来解决它。首先,我没有在发送方旋转帧。但是我旋转在媒体播放器中链接的TextureView并在接收器端调整textureview的大小。

我编码如下。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

 private void updateTextureViewSize(int viewWidth, int viewHeight) {
        int pivotPointX = viewWidth / 2;
        int pivotPointY = viewHeight / 2;

        Matrix matrix = new Matrix();

        if(isLandscapeOrientation) {
            matrix.preRotate(0);
            matrix.setScale(1.0f, 1.0f, pivotPointX, pivotPointY);
            videoView.setTransform(matrix);
            videoView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
        } else {
            matrix.preRotate(0);
            matrix.setScale(1.0f, 1.0f, pivotPointX, pivotPointY);
            videoView.setRotation(90);
            videoView.setTranslationX(-viewWidth / 2);
            videoView.setTranslationY(-viewHeight / 2);
            videoView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth * 2, viewHeight * 2));
        }

    }

private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Surface s = new Surface(surface);
            if(mMediaPlayer != null) {
                mMediaPlayer.setSurface(s);

                DisplayMetrics displaymetrics = new DisplayMetrics();
                getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
                int sh = displaymetrics.heightPixels;
                int sw = displaymetrics.widthPixels;

                updateTextureViewSize(sw, sh);

            }

        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    };
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

来自相机的 MediaCodec 视频流方向和颜色错误 的相关文章

  • 如何在Android 4.2中更改Action Bar选项菜单的背景颜色?

    我想更改 Android 4 2 中选项 溢出 菜单的背景颜色 我已经尝试了所有方法 但它仍然显示主题设置的默认颜色 我使用了以下代码和 XML 配置 MainActivity java public class MainActivity
  • PopupWindow onitemclick 中的 Android Listview 在某些设备上不起作用

    我的 ListView 在 PopupWindow 内 当我显示 PopupWindow 并单击设备 ASUS K00z 中的 Listview 行时 fonepad 工作得很好 但在 HTC Z715e 中不起作用 项目单击事件未触发 1
  • Facebook4j API:搜索

    我正在使用 Facebook4j 通过关键字获取状态 facebook4j conf ConfigurationBuilder fac new facebook4j conf ConfigurationBuilder fac setDebu
  • 在Java中测试服务器是否启动的正确方法?

    简单地查看是否可以建立与网站 服务器的连接的正确方法是什么 我想要这个用于我正在编码的应用程序 如果我的网站离线 它只会提醒我 Thanks 您可以使用 HttpURLConnection 发送请求并检查响应正文中是否有该页面特有的文本 而
  • Twowayview 滚动时自动添加内边距

    我在用双向视图 https github com lucasr twoway view在我的一个项目中 这是android的扩展回收者视图 https developer android com reference android supp
  • 对话框片段嵌入取决于设备

    在我的应用程序中 用户从联系人或通话记录中选择电话号码 选择联系人非常简单 并且在手机和平 板电脑上都可以很好地工作 i e 在手机上会弹出新的全屏活动 在桌子上我会看到带有联系人列表的漂亮弹出对话框 似乎无法从通话记录中选择电话号码 因此
  • 如何将Android中的cURL发送到REST服务

    我是 android 新手 我想从 REST 服务获取一些数据 但在初始化发送到 REST 服务的方法时遇到一些问题 您知道 REST 服务使用 cURL 来操作一些数据 POST PUT GET DELETE 现在如何在 android
  • 在新的菜单提供程序 API 弃用“setHasOptionsMenu”后,隐藏 Fragment 中的菜单项并在导航返回上再次显示它们

    大约一个月前 Android 团队弃用了onCreateOptionsMenu and onOptionsItemSelected 也setHasOptionsItemMenu 不幸的是 这破坏了我所有的代码 我的应用程序有很多片段 当用户
  • 如何在 Jetpack compose 中制作 FlipCard 动画

    我有一个现有的应用程序 我在其中使用 XML 中的 Objectanimator 实现了 FlipCard 动画 如下所示 如果我点击一张卡片 它会水平翻转 但现在我想将其迁移到 jetpack compose 那么jetpack comp
  • 使用Picasso从url保存图像?

    我正在尝试使用 API Picasso 保存图像 为了做到这一点 我正在尝试使用Target保存 但我无法完成这项工作 我怎么能这样做呢 Trying save image public static void imageDownload
  • Firebase Messaging FCM 在可配置的时间间隔内分发

    当您使用 FCM 向给定应用程序的所有设备发送推送时 这可能会导致许多用户同时打开他们的应用程序 从而导致大量服务器轮询 从而导致负载峰值 有没有一种方便的方法可以在给定的时间间隔内分发消息以进行计划推送 最后 我们找到了一种可能的方法 通
  • 在没有 BluetoothManagerCallback 的情况下调用 getBluetoothService

    我正进入 状态getBluetoothService called with no BluetoothManagerCallback在我的 Android 应用程序中经常出现错误 我不知道是什么原因导致这个或任何有关蓝牙管理器回调的事情 谁
  • Android WebView文件上传

    我正在开发一个 Android 应用程序 基本上它是一个WebView和一个进度条 Facebook 的移动网站 m facebook com 已加载到WebView 当我单击 选择文件 按钮上传图像时 没有任何反应 我已经尝试了所有的解决
  • 运行 Android 应用程序时出现错误

    我已经使用 Eclipse 创建了一个 Android 应用程序 但应用程序未在 AVD 上运行 它显示 不幸的是已停止工作 日志猫消息如下 07 29 04 59 50 789 W dalvikvm 784 threadid 1 thre
  • 如何为 flutter 绘图应用实现橡皮擦功能

    有一个关于通过 flutter 创建绘图应用程序的视频 YouTube https www youtube com watch v yyHhloFMNNA 它支持当用户点击屏幕时绘制线 点 但我找不到像 Android 本机那样擦除用户绘制
  • Android 10 请求 ACTIVITY_RECOGNITION 权限

    我试图遵守 Google 的要求 为 Android 10 请求 ACTIVITY RECOGNITION 权限 但我似乎不明白为什么没有显示权限弹出窗口 就像其他权限 即位置 存储等 一样 我的代码是 if ContextCompat c
  • 推特更新状态

    我正在通过 twitter4j 将 Twitter 集成到 Android 我可以成功阅读我发布的推文 现在我试图用它发布推文 但我不能 我收到如下奇怪的警告 02 01 16 28 43 298 WARN System err 729 4
  • 如果我的应用程序安装在 SD 卡上,私人数据也在那里吗?

    我假设应用程序的私有数据 例如 SharedPreferences 和 SQLite 数据库 位于手机的内部存储而不是 SD 卡上 即使应用程序本身安装在 SD 卡上 我在任何地方都找不到对此的简单明确的确认 有人可以确认一下吗 是的 私有
  • 如何检测日期选择器对话框的取消单击?

    我正在使用以下 日期选择器的示例 http developer android com guide tutorials views hello datepicker html http developer android com guide
  • Android Volley - 发布请求 - 无法在线工作

    我试图通过 Volley 发出 POST 请求 它在我的本地主机中工作得很好 但是当我将它移动到网络服务器时 响应为空 Java代码 RequestQueue queue Volley newRequestQueue this String

随机推荐

  • 请参阅 Java EE eclipse 调试中的 POST 参数

    我在调试 Java EE 方面没有经验 我更像是一个 javascript 人 我需要查看哪些 HTTP POST 参数到达服务器端 我在表单将其操作指向的 jsp 文件中放置了一个断点 现在我在调试变量窗口中找不到 POST 内容 他们在
  • java.lang.LinkageError:尝试重复的类定义

    为什么会发生错误以及如何修复它 02 13 02 pool 4 thread 2 WARN Exception in thread pool 4 thread 2 02 13 02 pool 4 thread 2 WARN java lan
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • 安装了 32 位的 Python,显示为 64 位

    我需要运行 32 位版本的 Python 我认为这就是我在我的机器上运行的 因为这是我下载的安装程序 当我重新运行安装程序时 它会将当前安装的 Python 版本称为 Python 3 5 32 位 然而当我跑步时platform arch
  • Ruby on Rails 服务器在 HTTPS POST 请求期间崩溃

    我正在尝试与你沟通城市飞艇API http urbanairship com docs push html broadcast使用 ROR Web 应用程序 在我的控制器中 我有以下代码 require net http require n
  • 如何在 QtQuick 2 中对 QML TableView 进行排序?

    我想使用 Qt 5 1 实现具有自定义角色的可排序 TableView 但我不知道当用户单击标题时该怎么做才能对其进行排序 在我的 Qt pro 文件中 我添加了 android ios blackberry qtHaveModule wi
  • Excel工作簿关闭后反复打开

    我使用了 Application ontime 方法来调度一些宏 关闭工作簿后 它会一次又一次地打开 为了解决这个问题 我在工作簿上设置了另一个事件 BeforeClosed 现在它显示运行时错误 1004 Object Applicati
  • 检查 TypeScript 中是否选中复选框元素

    我的 html 文件上有一个复选框
  • ReactiveUI 和 Caliburn Micro 一起?

    我一直在使用 Caliburn Micro 作为我们的 MVVM 框架对新的 Silverlight 应用程序进行一些原型工作 团队总体上对此感到满意 为了解决对服务的请求限制的一些问题 有人建议我研究 ReactiveUI 的 React
  • 正则表达式获取字符串中的第一个数字和其他字符

    我是正则表达式的新手 想知道如何才能只获取字符串中的第一个数字 例如100 2011 10 20 14 28 55 在这种情况下 我希望它返回100 但该数字也可以更短或更长 我在想类似的事情 0 9 但它单独获取每个数字 100 2001
  • 自动递增 ID 号 Google Apps 脚本

    我想在向工作表添加新值时自动增加 ID 我尝试从当前的 ID 列表创建一个列表 但它只计数到 5 因此当自动递增时 它只会到达一个点并为每个输入保存该数字 以下是我尝试获取 ID 号列表的方法 但它没有获取整个列表 我缺少什么 var ss
  • 将 time.Time 转换为字符串

    我正在尝试将数据库中的一些值添加到 string在围棋中 其中一些是时间戳 我收到错误 无法在数组元素中使用 U Created date 类型 time Time 作为类型字符串 我可以转换吗time Time to string typ
  • 通过 HttpClient 使用外部 REST Web 服务的存储库模式示例?

    我已经进行了相当多的搜索 但没有找到任何在 ASP NET MVC 应用程序中使用存储库模式使用外部 REST Web 服务的好示例 并且具有松散耦合和有意义的关注点分离 我在网上找到的几乎所有存储库模式示例都是编写 SQL 数据或使用 O
  • 频繁绘制 CGPath 时的性能

    我正在开发一个将数据可视化为折线图的 iOS 应用程序 该图被绘制为CGPath在全屏自定义中UIView最多包含 320 个数据点 数据经常更新 图表需要相应地重新绘制 刷新率为 10 秒就很好了 到目前为止很容易 然而 我的方法似乎需要
  • 如何结合pytube和tkinter标签来显示进度?

    我正在编写从 youtube 下载歌曲的小程序 使用 pytube 我想添加 python tkinter GUI 以在下载文件时显示百分比值 现在 当我执行代码时 程序首先下载文件 大约需要 60 秒 然后才显示 100 的标签 如果我希
  • Oracle SQL 函数中可以有 commit 语句吗

    在 SQL 函数中使用 COMMIT 语句是否可能 有意义 从技术上来说 答案是肯定的 你can请执行下列操作 create or replace function committest return number as begin upd
  • 为什么 h_addr_list (在 hostent 结构中)类型为 char** 而不是 void*?

    我试图找出一段构建 hostent 对象的代码 更具体地说 它的工作部分是填充 h addr list 数组 我对分配到数组中的值被强制转换为这一事实感到困惑char 据我所知 这个数组与字符串无关 我注意到h addr list数组实际上
  • 如何使用 Win32 API 与 com 端口 (RS232) 通信

    我正在尝试使用 win32 API 与 com 端口对话 我找到了这个http www robbayer com files serial win pdf http www robbayer com files serial win pdf
  • 如何在不饥饿的情况下锁定 std::mutex

    在我的程序中 我有一个互斥体和两个线程 这些线程之一经常获取锁 另一个线程尝试获取但必须永远等待 难道释放锁后获取锁的速度太快 以至于其他线程没有机会获得锁 互斥体总是给每个人一个机会吗 如果没有 什么是一个好的解决方案 某种 FIFO 锁
  • 来自相机的 MediaCodec 视频流方向和颜色错误

    我正在尝试流式传输视频捕获直接从相机适用于 Android 设备 到目前为止 我已经能够从 Android 相机捕获每一帧预览帧 byte data Camera camera 函数 对数据进行编码 然后成功解码数据并显示到表面 我用的是安