1. 程式人生 > >Android音視訊-視訊編解碼(H.264視訊硬編硬解)

Android音視訊-視訊編解碼(H.264視訊硬編硬解)

在前面接觸了音訊的編解碼,學習了通過MediaCodec來進行硬編碼。把AudioRecord 採集的到的PCM音訊資料編碼壓縮為AAC格式的音訊資料,然後解碼為PCM通過AudioTrack來播放。參考Demo連結

前面我們可以很形象的瞭解音訊資料,然後如何來編解碼音訊資料,並且操作這些資料。對於視訊的資料的編解碼也希望可以有那樣形象的理解。

在前面文章中Camera預覽中我們直接使用高階API MediaRecord來進行視訊的錄製包括視訊和音訊資料的錄製。它遮蔽了我們對於底層到底是如何編碼完成的的實現細節。但是我們作為開發者要了解實現底層的實現細節。

本文實現功能:

  • 通過Camera採集NV21資料編碼為H.264視訊檔案並儲存
  • 通過Camera2採集YV12資料編碼為H.264視訊檔案並儲存
  • 通過SurfaceView解碼顯示Camera編碼儲存的H.264視訊檔案
  • 通過TextureView解碼顯示Camera編碼儲存的H.264視訊檔案

視訊編解碼基礎

這個我們在前面已經瞭解過,這裡再稍微回顧一下,視訊的編碼我們仔細學習視訊的編碼格式的編解碼。封裝視訊的格式的編解碼主流的有H.26X系列和MPEG系列,我們前面使用Camera來錄製視訊的時候就設定了MediaRecord的音視訊的編解碼。程式碼回顧:

mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder
.MPEG_4_SP); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

我們通過MediaRecorder設定了視訊編碼為MPEG 系列來編碼,錄製完成的視訊拿出來看一下詳情是不是MPEG的編碼。


這裡寫圖片描述

可以看到是我們設定的音視訊的編碼方式。

這個是Android的高階API來做的,底層的實現細節都遮蔽了,我們不止要知道這些,還要和音訊一樣知道簡單的PCM資料如何編解碼成我們要的資料格式。

視訊編碼

我們同樣也和音訊的原始資料PCM資料編碼很為wav類似的過程。對於視訊資料我們前面通過攝像頭採集到的視訊資料資料是YUV或者RGB格式的。這種資料格式因為是原始的資料,它就會很大了,我們於是通過各種編碼方式可以壓縮視訊資料的大小。並且不使用MediaRecorder來做,而是通過偏向於底層資料一點的MediaCodec來自己編碼視訊資料。

  • 我們要實現的功能位通過Camera採集到每幀YUV原始資料
  • 錄製視訊後,編碼YUV原始資料為H.264視訊格式
  • 儲存H.264編碼格式的視訊檔案

編碼YUV為H.26X編碼視訊格式

我們採用的都是硬編硬解的方式來完成功能,主要是熟悉API的使用。
剛開始我想要通過Camera2來編碼相機預覽資料為H.264檔案,這其中經歷了很多波折,難點在預覽資料的格式轉換上面。對於幾個基本的資料格式的概念我們要好好的瞭解一下。

YUV資料

YUV通過Y,U,V三個分量表示顏色空間,Y表示亮度,UV表示色度。RGB顏色空間每個畫素點都有獨立的RGB三個顏色分量值。YUV卻不同:
YUV根據UV取樣數目的不同,分為YUV444,YUV422,YUV420等。

YUV420

表示每個畫素點有一個獨立的亮度表示,即Y;色度UV分量由每四個畫素點共享一個。例如一個4X4的圖片,在YUV420格式下,有16個Y,UV各四個。
YUV420根據UV色度的儲存順序不同,又分為不同的格式。它分為兩個YUV420P和YUV420SP兩個大類,YUV420P的UV順序儲存,YUV420SP的UV交錯儲存。以4X4的圖片格式為例部分格式如下:

名稱 資料儲存順序 所屬大類
I420 YYYYYYYYYYYYYYYYUUUUVVVV YUV420P
YV12 YYYYYYYYYYYYYYYYVVVVUUUU YUV420P
NV12 YYYYYYYYYYYYYYYYVUVUVUVU YUV420SP
NV21 YYYYYYYYYYYYYYYYUVUVUVUV YUV420SP

編碼主要程式碼

初始化MediaCodec

public AvcEncoder(int width, int height, int framerate, File outFile,boolean isCamera) {
        this.mIsCamera = isCamera;
        mWidth = width;
        mHeight = height;
        mFrameRate = framerate;
        mOutFile = outFile;

        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        try {
            mMediaCodec = MediaCodec.createEncoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mMediaCodec.start();
        createfile();
    }

開始編碼:

public void startEncoderThread() {
        Thread encoderThread = new Thread(new Runnable() {

            @SuppressLint("NewApi")
            @Override
            public void run() {
                isRunning = true;
                byte[] input = null;
                long pts = 0;
                long generateIndex = 0;

                while (isRunning) {
                    if (mYuvQueue.size() > 0) {
                        input = mYuvQueue.poll();
                        if(mIsCamera){
                            //NV21資料所需空間為如下,所以建立如下緩衝區
                            byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
                            NV21ToNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        }else{
                            byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
                            YV12toNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        }
                    }
                    if (input != null) {
                        try {
                            ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

                            int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
                            if (inputBufferIndex >= 0) {
                                pts = computePresentationTime(generateIndex);
                                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                                inputBuffer.clear();
                                inputBuffer.put(input);
                                mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
                                generateIndex += 1;
                            }

                            ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
                            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                            int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

                            while (outputBufferIndex >= 0) {
                                //Log.i("AvcEncoder", "Get H264 Buffer Success! flag = "+bufferInfo.flags+",pts = "+bufferInfo.presentationTimeUs+"");
                                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                byte[] outData = new byte[bufferInfo.size];
                                outputBuffer.get(outData);
                                if (bufferInfo.flags == 2) {
                                    mConfigByte = new byte[bufferInfo.size];
                                    mConfigByte = outData;
                                } else if (bufferInfo.flags == 1) {
                                    byte[] keyframe = new byte[bufferInfo.size + mConfigByte.length];
                                    System.arraycopy(mConfigByte, 0, keyframe, 0, mConfigByte.length);
                                    System.arraycopy(outData, 0, keyframe, mConfigByte.length, outData.length);
                                    outputStream.write(keyframe, 0, keyframe.length);
                                } else {
                                    outputStream.write(outData, 0, outData.length);
                                }

                                mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                                outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                            }

                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        encoderThread.start();
    }

停止編碼

public void stopThread() {
        if (!isRunning) return;
        isRunning = false;
        try {
            stopEncoder();
            if (outputStream != null) {
                outputStream.flush();
                outputStream.close();
                outputStream = null;
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

這個使用MediaCodec的程式碼編碼參考自這裡

我這裡做了一個小的區分:

  • 在使用Camera的時候,設定預覽的資料格式為NV21
  • 在使用Camera2的時候,無法支援預覽資料為NV21,我設定的預覽資料格式YV12

設定以後為什麼做如下程式碼處理?

if (mYuvQueue.size() > 0) {
                        input = mYuvQueue.poll();
                        if(mIsCamera){
                            //NV12資料所需空間為如下,所以建立如下緩衝區
                            //y=W*h;u=W*H/4;v=W*H/4,so total add is W*H*3/2
                            byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
                            NV21ToNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        }else{
                            byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
                            YV12toNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        }
                    }

可以看到我把Camera的NV21的預覽資料和Camera2的YV12預覽資料都轉換成了NV12格式的資料。並且我在編碼為H.264視訊的時候圖片沒有出現有一點綠色在上面的情況。所以我得出這麼一個結論:

H.264編碼必須要用NV12,所以我們拿到預覽資料要做格式轉換
這個結論和我參考的一些文章的結論不同,但是我的實踐程式碼是我的這個結論的程式碼成功的輸出了格式良好的H.264檔案,暫且認為我的結論是正確的。

ok終於搞完了這個東西,至於獲取預覽資料的程式碼就參考之前的實現了Camera有回掉很簡單,Camera2通過ImageRender來獲取。生成的視訊通過VLC播放器清晰的顯示出來了,很高興。完整Demo在最下面貼出。

視訊解碼

視訊的解碼我感覺遇到的問題就是對於視訊的格式的處理。而對於視訊的MediaCodec解碼我感覺我遇到的問題就是對於輸出顯示的視訊的寬高的概念問題開始模糊了。

解碼主要程式碼

初始化MediaCodec

public void initCodec() {
        File f = new File(mFilePath);
        if (null == f || !f.exists() || f.length() == 0) {
            Toast.makeText(mContext, "指定檔案不存在", Toast.LENGTH_LONG).show();
            return;
        }
        try {
            //獲取檔案輸入流
            mInputStream = new DataInputStream(new FileInputStream(new File(mFilePath)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try {
            //通過多媒體格式名建立一個可用的解碼器
            mCodec = MediaCodec.createDecoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //初始化編碼器
        final MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", mVideoWidth, mVideoHeight);
        //獲取h264中的pps及sps資料
        if (isUsePpsAndSps) {
            byte[] header_sps = {0, 0, 0, 1, 103, 66, 0, 42, (byte) 149, (byte) 168, 30, 0, (byte) 137, (byte) 249, 102, (byte) 224, 32, 32, 32, 64};
            byte[] header_pps = {0, 0, 0, 1, 104, (byte) 206, 60, (byte) 128, 0, 0, 0, 1, 6, (byte) 229, 1, (byte) 151, (byte) 128};
            mediaformat.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
            mediaformat.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));
        }
        //設定幀率
        mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
        mCodec.configure(mediaformat, mSurface, null, 0);
    }

開啟解碼執行緒

public class decodeH264Thread implements Runnable {
        @Override
        public void run() {
            try {
                decodeLoop();
            } catch (Exception e) {
            }
        }

        private void decodeLoop() {
            //存放目標檔案的資料
            ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
            //解碼後的資料,包含每一個buffer的元資料資訊,例如偏差,在相關解碼器中有效的資料大小
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            long startMs = System.currentTimeMillis();
            long timeoutUs = 10000;
            byte[] marker0 = new byte[]{0, 0, 0, 1};
            byte[] dummyFrame = new byte[]{0x00, 0x00, 0x01, 0x20};
            byte[] streamBuffer = null;
            try {
                streamBuffer = getBytes(mInputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
            int bytes_cnt = 0;
            while (mStartFlag == true) {
                bytes_cnt = streamBuffer.length;
                if (bytes_cnt == 0) {
                    streamBuffer = dummyFrame;
                }

                int startIndex = 0;
                int remaining = bytes_cnt;
                while (true) {
                    if (remaining == 0 || startIndex >= remaining) {
                        break;
                    }
                    int nextFrameStart = KMPMatch(marker0, streamBuffer, startIndex + 2, remaining);
                    if (nextFrameStart == -1) {
                        nextFrameStart = remaining;
                    } else {
                    }

                    int inIndex = mCodec.dequeueInputBuffer(timeoutUs);
                    if (inIndex >= 0) {
                        ByteBuffer byteBuffer = inputBuffers[inIndex];
                        byteBuffer.clear();
                        byteBuffer.put(streamBuffer, startIndex, nextFrameStart - startIndex);
                        //在給指定Index的inputbuffer[]填充資料後,呼叫這個函式把資料傳給解碼器
                        mCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);
                        startIndex = nextFrameStart;
                    } else {
                        continue;
                    }

                    int outIndex = mCodec.dequeueOutputBuffer(info, timeoutUs);
                    if (outIndex >= 0) {
                        //幀控制是不在這種情況下工作,因為沒有PTS H264是可用的
                        while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        boolean doRender = (info.size != 0);
                        //對outputbuffer的處理完後,呼叫這個函式把buffer重新返回給codec類。
                        mCodec.releaseOutputBuffer(outIndex, doRender);
                    }
                }
                mStartFlag = false;
                mHandler.sendEmptyMessage(0);
            }
        }
    }

停止解碼執行緒

public void stopDecodingThread() {
        mStartFlag = false;
        if (mCodec != null) {
            mCodec.stop();
            mCodec = null;
            try {
                mDecodeThread.join();
                mDecodeThread = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

以上H.264解碼的關鍵程式碼參考自這篇博文

SurfaceView呼叫解碼程式碼

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_decode_h264_to_surface_view);
        mSurfaceView = findViewById(R.id.surfaceview);
        mSurfaceView.setKeepScreenOn(true);
        SurfaceHolder holder = mSurfaceView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (mAvcDecoder == null) {
                    File file = getExternalFilesDir(Environment.DIRECTORY_MOVIES);
                    File[] files = null;
                    if (file.exists()) {
                        files = file.listFiles(new FileFilter() {
                            @Override
                            public boolean accept(File pathname) {
                                return pathname.getAbsolutePath().endsWith(".h264");
                            }
                        });
                    }

                    if (files != null && files.length > 0) {
                        mFile = files[0];
                    }

                    if (mFile == null) {
                        Toast.makeText(DecodeH264ToSurfaceViewActivity.this, "視訊檔案不存在,先生成", Toast.LENGTH_SHORT).show();
                        return;
                    }

                    mAvcDecoder = new AVCDecoderToSurface(mHandler,
                            DecodeH264ToSurfaceViewActivity.this, mFile.getAbsolutePath(),
                            holder.getSurface(), 1080, 1920, 30);
                    mAvcDecoder.initCodec();
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                Log.e(TAG, "surfaceChanged: ");
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                Log.e(TAG, "surfaceDestroyed: ");
            }
        });
        mButton = findViewById(R.id.button);
    }

問題描述

我使用SurfaceView顯示解碼的資料可以顯示出來,但是視訊是一個在豎直螢幕下先左邊旋轉90度的顯示方式?為什麼這樣呢?並且我的視訊傳送到電腦端通過VLC顯示也是一個豎直螢幕方向左邊選擇90的的方式播放?

出現這個問題我第一個想到的是把這個SurfaceView順時針轉90度應該就可以了吧,於是使用TextureView來顯示解碼的資料,關鍵程式碼如下:

private void initTextureView() {
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//                Matrix matrix = new Matrix();
//                matrix.postRotate(90, mTextureView.getWidth() / 2, mTextureView.getHeight() / 2);
//                mTextureView.setTransform(matrix);
                if (mAvcDecoder == null) {
                    File file = getExternalFilesDir(Environment.DIRECTORY_MOVIES);
                    File[] files = null;
                    if (file.exists()) {
                        files = file.listFiles(new FileFilter() {
                            @Override
                            public boolean accept(File pathname) {
                                return pathname.getAbsolutePath().endsWith(".h264");
                            }
                        });
                    }

                    if (files != null && files.length > 0) {
                        mFile = files[0];
                    }

                    if (mFile == null) {
                        Toast.makeText(DecodeH264ToTextureViewActivity.this, "視訊檔案不存在,先生成", Toast.LENGTH_SHORT).show();
                        return;
                    }

                    mAvcDecoder = new AVCDecoderToSurface(mHandler,
                            DecodeH264ToTextureViewActivity.this, mFile.getAbsolutePath(),
                            new Surface(surface), 1920, 1080, 30);
                    mAvcDecoder.initCodec();
                }
            }

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

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                if (mAvcDecoder != null) {
                    mAvcDecoder.stopDecodingThread();
                }
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {

            }
        });
    }

以為可以見證效果,視訊確實是轉了90度“正”了。但是顯示的寬高不對,並且嚴重拉扯了。怎麼樣的呢,圖片描述一下:



我感覺這個問題出現的原因得從源來看,我上面是希望從輸出端來調正。我的視訊解碼只從Camera的預覽資料Demo測試通過。所以再回顧看看設定的Camera的相關寬高的程式碼

....
//設定預覽尺寸onPreviewFrame的尺寸
            parameters.setPreviewSize(mBestSize.width, mBestSize.height);

            //設定拍照輸出圖片尺寸
            parameters.setPictureSize(mBestSize.width, mBestSize.height);

            int rotationDegrees = getCameraDisplayOrientation((Activity) mContext, mCameraId);
            Log.e(TAG, "initCamera: rotation degrees=" + rotationDegrees);
            mCamera.setDisplayOrientation(rotationDegrees);

            parameters.setPreviewFormat(ImageFormat.NV21);
....
@Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        switch (mState) {
            case STATE_PREVIEW:
                if (mAvcEncoder != null) {
                    mAvcEncoder.stopThread();
                    mAvcEncoder = null;
                    Toast.makeText(mContext, "停止錄製視訊成功", Toast.LENGTH_SHORT).show();
                }
                break;
            case STATE_RECORD:
                Log.e(TAG, "onPreviewFrame: record video");
                if (mAvcEncoder == null) {
                    mAvcEncoder = new AvcEncoder(mBestSize.width,
                            mBestSize.height, mFrameRate,
                            getOutputMediaFile(MEDIA_TYPE_VIDEO), true);
                    mAvcEncoder.startEncoderThread();
                    Toast.makeText(mContext, "開始錄製視訊成功", Toast.LENGTH_SHORT).show();
                }

                mAvcEncoder.putYUVData(data);
                break;
        }
    }

反思以上的程式碼,我設定了Camera的mCamera.setDisplayOrientation方法旋轉90度,但是要注意的是在onPreviewFrame中每一幀的資料並沒有選擇90度,並且我們設定MediaCodec的寬高,它決定了我們編碼的視訊的檔案的最終的寬高的大小。這就解釋了我們的視訊放到PC端的VLC上面播放是一個左邊選擇90度的形式來播放的。

原來如此,就是我的編碼的源視訊檔案的每一幀的資料是在豎直方向下同Camera Sensor的資料,它要經過順時針轉90度才是我們物理世界實際看到的效果,通過程式碼選擇一下,並且設定編碼的時候的寬度和高度對掉一下,OK。完美解決我們豎直螢幕下解碼播放視訊的問題。

我們獲取的一個最佳的寬高是寬度高於高度的,這個是通過Camera的API來獲取的,而它的計量是通過Camera Sensor的左邊來看的。所以我們獲取並且設定的最佳的顯示寬高例如這樣的1920X1080的。

注意我解碼的視訊的原始檔是通過Camera預覽生成視訊的Demo中來的,沒有去看Camera2的預覽生成的原始檔的解碼過程了,並且我使用的是小米5 Android7.0系統。通過Camera相關API獲取到一個最佳的顯示寬高大小是1920X1080,而通過Camera2相關APi獲取最佳的寬高為1440X1080,在解碼視訊檔案的時候我固定的寫了解碼MediaCodec的寬高為1920X1080

相關推薦

Android視訊-視訊解碼(H.264視訊)

在前面接觸了音訊的編解碼,學習了通過MediaCodec來進行硬編碼。把AudioRecord 採集的到的PCM音訊資料編碼壓縮為AAC格式的音訊資料,然後解碼為PCM通過AudioTrack來播放。參考Demo連結 前面我們可以很形象的瞭解音訊資料,然後如何

基於Arria10的H.264 4K高效能解碼器模組視訊演示

更多精彩內容,請微信搜尋“FPGAer俱樂部”關注我們。位於加拿大溫哥華的System-On-Chip Technologies公司正式釋出基於Arria10 SOC器件的H.264高效能編解碼模組,同時提供對應的評估套件。基於Arria10 SOC器件的H.264高效能編碼

Android視訊編輯器(五)音訊解碼、從視訊中分離音訊、音訊混、音訊音量調節等

/** * 歸一化混音 * */ public static byte[] normalizationMix(byte[][] allAudioBytes){ if (allAudioBytes == null || allAudioBytes.length

即時通訊視訊開發(二):視訊解碼之數字視訊介紹

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 本文是系列文章中的第2篇,本系列文章的大綱如下:   《即時

ffmpeg實現H.264視訊解碼-1

▶ ffmpeg是一個優秀的開源多媒體編解碼集合 ▶ ffmpeg的libavcodec完成音視訊的編碼或解碼 ▶ H.264視訊解碼主要由H264.c實現 ▶ H264.c能夠流暢解碼x264編碼工程的碼流 ▶ ffmpeg的H.264解碼過程包括初始

H.264視頻解碼器——參考軟件JM的下載與解碼

cfg 源文件 ict 兩個 control href eid inpu byte H.264視頻編解碼器——參考軟件JM的下載與編解碼 一、下載JM工程: JM是H.264標準制定團隊所認可的官方參考軟件。網址如下 http://iphome.hhi.de/suehri

【視頻解碼·學習筆記】3. H.264視頻解碼工程JM的下載與解碼

tro 關鍵幀 isa 源文件 完成 -m decode orm mat 一、下載JM工程: JM是H.264標準制定團隊所認可的官方參考軟件。網址如下 http://iphome.hhi.de/suehring/tml/ 從頁面中可找到相應的工程源碼,本次選擇JM 8

使用MediaCodec解碼h.265視訊及音訊進行播放

h.265這個視訊是很多播放器不支援的,就算是bilibili開源的ijkplayer也不能直接播放,需要自己去重新編譯 才可以支援。 這裡通過這個demo來演示一下如何硬解碼視訊,播放h.265視訊,其實編碼的視訊同樣道理。 視訊的播放主要在surfaceView中顯示,而解碼

H.264視訊編碼推薦的解析度和位元速率配置表

在各種視訊編碼標準中,行業一直在求追“高壓縮比(資料量越小越好)”,同時又保證“高視訊質量”的演算法。魚和熊掌不可兼得,視訊編碼是一種折中的遊戲。引數“視訊位元速率”的設定,就代表了這種折中的選擇。位元速率越高,資料量越大,視訊質量越好,位元速率越小,資料量越小

實現RTP協議的H.264視訊傳輸系統

前面分別討論了RTP 協議及H.264基本流的結構,那麼如何使用RTP協議來傳輸H.264視訊了?一個有效的辦法就是從H.264視訊中剝離出每個NALU,在每個NALU前新增相應的RTP包頭,然後將包含RTP 包頭和NALU 的資料包傳送出去。下面就從RTP包頭和NALU兩方面分別闡述。       完整的

h.264視訊檔案封裝

+++++一個完整的多媒體檔案是由音訊和視訊2部分組成的。H264、Xvid等就是視訊編碼格式,MP3、AAC等就是音訊編碼格式。字幕檔案只是其中附帶部分。    把視訊編碼和音訊編碼打包成一個完整的多媒體檔案,可以有不同的方式,這種方式便是所謂的封裝方式,不同的封裝方式有不同的字尾。由於有些封裝方式具有很

H.264視訊碼流解析

原理 H.264原始碼流(又稱為“裸流”)是由一個一個的NALU組成的。他們的結構如下圖所示。 其中每個NALU之間通過startcode(起始碼)進行分隔,起始碼分成兩種:0x00000

【譯】解決HTML5 MP4(H.264)視訊模糊的問題

MP4視訊檔案是所有主要的現代瀏覽器都認可和接受的檔案格式。如果你想知道具體的支援情況,請檢視 Can I Use 但是!當 HTML5視訊標籤Video 嵌入網頁中,如果沒有正確配置好你的MP4視訊會得到一個模糊影象的視訊。發生這種情況的原因是,這種型別的視

使用jrtplib傳輸H.264視訊檔案(3)

介紹如何傳送h264,下面我們介紹下如何接收h264檔案。 其中主要關注的就是被拆分的資料包的重組,下面的程式碼中有詳盡的註釋。 class CRTPReceiver : public RTPSession   {   protected:       vo

如何推送h.264視訊直播流到rtmp server

如果要單純的以h.264的碼流推送攝像頭的視訊到rtmp server(ip:127.0.0.1),可以這樣: ffmpeg -f video4linux2 -vcodec libx264 -vpre default  -s 320x240 -i /dev/video0

基於srs-librtmp釋出H.264視訊檔案到伺服器

開發平臺:Linux說明:srs提供了釋出H.264的例子,在例子程式碼檔案為srs_h264_raw_publish.c,例子呼叫srs-librtmp函式庫即可實現H.264檔案釋出到伺服器。srs-librtmp為VS2010工程,在Linux下可以直接使用原始碼,在s

使用jrtplib(RTP)傳輸H.264視訊檔案(1)

繼續上一篇部落格,在使用x264對視訊進行編碼之後,我們需要將編碼好的視訊進行網路傳輸,傳送給其他的客戶端,之後再進行解碼。那麼,這篇部落格我將介紹下如何使用jrtplib這個庫對編碼後的檔案傳輸。 RTP協議基礎知識: 首先,我們依然要先了解一些關於協議的基礎知

幾個平臺環境裏視頻解碼和圖像scale的件加速的方法

and -c med hub 開發 roi ace 擴展 all 記錄一下遇到幾個平臺裏的視頻編解碼和圖像scale的硬件加速的方法 1,intel平臺可基於VA-API實現視頻codec和圖像scale的硬件加速,具體可使用libyami這個接口友好的封裝庫。加速處理過

VB URL的解碼原始碼 GB2312 UTF-8解碼

介面如下 原始碼如下 'UTF-8 URL解碼 Public Function UTF8_UrlDecode(ByVal URL As String) Dim B, ub ''中文字的Unicode碼(2位元組) Dim AA, BB Dim Ut

python3 解碼常見操作(codecs, url解碼, soup例項化報錯)

codecs是encoders和decoders的縮寫。codecs模組為我們解決的字元編碼的處理提供了lookup方法,它接受一個字元編碼名稱的引數,並返回指定字元編碼對應的codecs.CodecInfo 物件,該物件包含了 encoder、decoder、StreamR