MediaCodec API,完成视频 H.264 的硬编、硬解

一、编码基础概念

1.1 为什么要进行视频编码

视频是由一帧帧图像组成,就如常见的gif图片,如果打开一张gif图片,可以发现里面是由很多张图片组成。一般视频为了不让观众感觉到卡顿,一秒钟至少需要24帧画面(一般是30帧),假如该视频是一个1280x720分辨率的视频,那么不经过编码一秒钟的大小:
结果:1280x720x4x24/(1024*1024)≈84.375M

所以不经过编码的视频根本没法保存,更不用说传输了。

1.2 视频压缩编码标准

视频中存在很多冗余信息,比如图像相邻像素之间有较强的相关性,视频序列的相邻图像之间内容相似,人的视觉系统对某些细节不敏感等,对这部分冗余信息进行处理的过程就是视频编码。

H.26X系列(由ITU[国际电传视讯联盟]主导)
H.261:主要在老的视频会议和视频电话产品中使用
H.263:主要用在视频会议、视频电话和网络视频上
H.264:H.264/MPEG-4第十部分,或称AVC(Advanced Video Coding,高级视频编码),是一种视频压缩标准,一种被广泛使用的高精度视频的录制、压缩和发布格式。
H.265:高效率视频编码(High Efficiency Video Coding,简称HEVC)是一种视频压缩标准,H.264/MPEG-4 AVC的继任者。可支持4K分辨率甚至到超高画质电视,最高分辨率可达到8192×4320(8K分辨率),这是目前发展的趋势,尚未有大众化编码软件出现

MPEG系列(由ISO[国际标准组织机构]下属的MPEG[运动图象专家组]开发)
MPEG-1第二部分:MPEG-1第二部分主要使用在VCD上,有些在线视频也使用这种格式
MPEG-2第二部分(MPEG-2第二部分等同于H.262,使用在DVD、SVCD和大多数数字视频广播系统中
MPEG-4第二部分(MPEG-4第二部分标准可以使用在网络传输、广播和媒体存储上

1.3 编码流程

在进行当前信号编码时,编码器首先会产生对当前信号做预测的信号,称作预测信号(predicted signal)

预测的方式

时间上的预测(interprediction),亦即使用先前帧的信号做预测
空间上的预测 (intra prediction),亦即使用同一张帧之中相邻像素的信号做预测
得到预测信号后,编码器会将当前信号与预测信号相减得到残余信号(residual signal),并只对残余信号进行编码,如此一来,可以去除一部份时间上或是空间上的冗余信息。

编码器并不会直接对残余信号进行编码,而是先将残余信号经过变换(通常为离散余弦变换)然后量化以进一步去除空间上和感知上的冗余信息。量化后得到的量化系数会再透过熵编码,去除统计上的冗余信息。

二、H.264编码详解(AVC)

2.1 H.264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称。

相关理解:

在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,所以对于一段变化不大图像画面,我们可以先编码出一个完整的图像帧A,随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小,B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。
这段图像我们称为一个序列,序列就是有相同特点的一段数据,当某个图像与之前的图像变化很大,无法参考前面的帧来生成,那我们就结束上一个序列,开始下一段序列,也就是对这个图像生成一个完整帧A1,随后的图像就参考A1生成,只写入与A1的差别内容。

主要目标:

高的视频压缩比;
良好的网络亲和性;

2.2 H.264三种帧

在H.264中定义了三种帧:
    I帧:完整编码的帧叫I帧
    P帧:参考之前的I帧生成的只包含差异部分编码的帧叫P帧
    B帧:参考前后的帧编码的帧叫B帧

H264采用的核心算法是帧内压缩和帧间压缩:
    帧内压缩是生成I帧的算法
    帧间压缩是生成B帧和P帧的算法

压缩方法:
    分组:把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多
    定义帧:将每组内各帧图像定义为三种类型,即I帧、B帧和P帧;
    预测帧:以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧;
    数据传输:最后将I帧数据与预测的差值信息进行存储和传输。

GOP序列:
    在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流。
    一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像

2.3 编码YUV为H.26X编码视频格式

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的图片格式为例部分格式如下:


YUV420

三、H.264分层设计

3.1 H264算法在概念上分为两层:

1、VCL:(Video Coding Layer)视频编码层,负责高效的内容表示。
2、NAL:(Network Abstraction Layer)网络提取层,负责以网络所要求的恰当的方式对数据进行打包和传送。

VCL数据即被压缩编码后的视频数据序列。在VCL数据要封装到NAL单元中之后,才可以用来传输或存储。

H.264 的编码视频序列包括一系列的NAL 单元,每个NAL 单元包含一个RBSP。一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 Start Code 用于标示这是一个NALU 单元的开始,必须是"00 00 00 01" 或"00 00 01"。


NAL单元序列

其中RBPS有分为几种类型:


典型的RBSP单元

3.2 NAL头

占一个字节(8bit),由三部分组成forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)。

3.2.1 forbidden_bit

禁止位,H264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码。

3.2.2 nal_reference_bit

当前NAL的优先级,值越大,该NAL越重要。

3.2.3 nal_unit_type

它表示NALU Header后面的RBSP的数据结构的类型。下图为nal_unit_type所有可能的取值,和对应的语义:


nal_unit_type语义

可以看到,nal_unit_type的值为1-5时,表示RBSP里面包含的数据为条带(片/Slice)数据,所以值为1-5的NALU统称为VCL(视像编码层)单元,其他的NALU则称为非VCL NAL单元。

当nal_unit_type为7时,代表当前NALU为序列参数集,为8时为图像参数集。这也是我们打开.h264文件后,遇到的前两个NALU,它们位于码流的最前面。

而且当nal_unit_type为14-31时,我们可以不用理睬,目前几乎用不到。

PPS(Picture Parameter Sets):图像参数集
SPS(Sequence Parameter Set):序列参数集

接下来说说SPS、PPS和SEI。

SPS和PPS是用来初始化解码器的,没这些数据,视频数据是无法解析出来的。另外如果我们分析单独的H264文件,可以发现有的文件每个IDR帧前面都有PPS和SPS,有的只是开头才有。针对SPS和PPS,一般来说:
1)、如果是在直播的话,每个IDR帧前面都应该加上SPS和PPS,因为有的观众会中途进来观看。
2)、如果是本地稳定文件,可以在开头加上SPS和PPS,或者都加上,这个根据具体需要来。

另外说明下SEI,有的H264文件有SEI,有的则没有,这说明SEI对文件的播放并无太大影响。
SEI(Supplemental Enhancement Information):辅助增强信息。这里面可以存放一些影片简介,版权信息或者作者自己添加的一些信息。

四、实例

4.1 MediaCodec解码本地视频 H.264文件,然后在SurfaceView上预览

4.1.1 从本地H264文件中获取文件流

    public void getFileInputStream() {
        try {
            File file = new File(H264_FILE);
            mInputStream = new DataInputStream(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            try {
                mInputStream.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

4.1.2 初始化解码器

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void initMediaCodec() {
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    //创建编码器
                    mCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //初始化编码器
                final MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,holder.getSurfaceFrame().width(),holder.getSurfaceFrame().height());
                /*h264常见的帧头数据为:
                00 00 00 01 67    (SPS)
                00 00 00 01 68    (PPS)
                00 00 00 01 65    (IDR帧)
                00 00 00 01 61    (P帧)*/

                //获取H264文件中的pps和sps数据
                if (UseSPSandPPS) {
                    byte[] header_sps = {0, 0, 0, 1, 67, 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, 68, (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,40);
                mCodec.configure(mediaFormat,holder.getSurface(),null,0);

                startDecodingThread();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {

            }
        });
    }

4.1.3 开启新的线程进行解码

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private class DecodeThread implements Runnable{

        @Override
        public void run() {
            //循环解码
            decodeLoop();
        }


        private void  decodeLoop(){
            //获取一组输入缓存区
            ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
            //解码后的数据,包含每一个buffer的元数据信息
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            long startMs = System.currentTimeMillis();
            long timeoutUs = 10000;
            //用于检测文件头
            byte[] maker0 = 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 (mStopFlag == false){
                //得到可用字节数组长度
                bytes_cnt = streamBuffer.length;

                if (bytes_cnt == 0){
                    streamBuffer = dummyFrame;
                }
                int startIndex = 0;
                //定义记录剩余字节的变量
                int remaining = bytes_cnt;
                while (true) {
                    //当剩余的字节=0或者开始的读取的字节下标大于可用的字节数时  不在继续读取
                    if (remaining == 0 || startIndex >= remaining) {
                        break;
                    }
                    //寻找帧头部
                    int nextFrameStart = KMPMatch(maker0,streamBuffer,startIndex + 2,remaining);
                    //找不到头部返回-1
                    if (nextFrameStart == -1) {
                        nextFrameStart = remaining;
                    }
                    //得到可用的缓存区
                    int inputIndex = mCodec.dequeueInputBuffer(timeoutUs);
                    //有可用缓存区
                    if (inputIndex >= 0) {
                        ByteBuffer byteBuffer = inputBuffers[inputIndex];
                        byteBuffer.clear();
                        //将可用的字节数组,传入缓冲区
                        byteBuffer.put(streamBuffer, startIndex, nextFrameStart - startIndex);
                        //把数据传递给解码器
                        mCodec.queueInputBuffer(inputIndex, 0, nextFrameStart - startIndex, 0, 0);
                        //指定下一帧的位置
                        startIndex = nextFrameStart;
                    } else {
                        continue;
                    }

                    int outputIndex = mCodec.dequeueOutputBuffer(info,timeoutUs);
                    if (outputIndex >= 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(outputIndex, doRender);
                    } else {

                    }
                }
                mStopFlag = true;
            }
        }
    }

源码地址:https://github.com/Xiaoben336/MediaCodecDecodeH264Demo

4.2 收集Camera数据,并转码为H264存储到文件

4.2.1 实现方式

视频采集用Camera 2采集YV12数据编码为H264视频文件并保存。
1、我们要实现的功能位通过Camera采集到每帧YUV原始数据
2、录制视频后,编码YUV原始数据为H.264视频格式
3、保存H.264编码格式的视频文件

在使用Camera的时候,设置预览的数据格式为NV21
在使用Camera2的时候,无法支持预览数据为NV21,我设置的预览数据格式YV12

4.2.2 编码实现

4.2.2.1 初始化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);//YUV420SP
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);//*5表示较高的比特率,也可以是1、3
        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();//创建H264文件的保存位置
    }

4.2.2.2 开始编码

public boolean isRunning = false;
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void startEncoderThread(){
        Thread encoderThread = new Thread(new Runnable() {
            @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) {//Camera  NV21
                            //NV12数据所需空间为如下,所以建立如下缓冲区
                            //y=W*h;u=W*H/4;v=W*H/4,so total add is W*H*3/2 (1 + 1/4 + 1/4 = 3/2)
                            byte[] yuv420sp = new byte[mWidth * mHeight *3 /2];
                            NV21ToNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        } else {//Camera 2
                            byte[] yuv420sp = new byte[mWidth * mHeight *3 /2];
                            YV12toNV12(input, yuv420sp, mWidth, mHeight);
                            input = yuv420sp;
                        }
                    }

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

                        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo,TIMEOUT_USEC);
                        while (outputBufferIndex >= 0) {
                            ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(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);
                                try {
                                    outputStream.write(keyframe, 0, keyframe.length);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            } else {
                                try {
                                    outputStream.write(outData, 0, outData.length);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }

                            mMediaCodec.releaseOutputBuffer(outputBufferIndex,false);
                            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo,TIMEOUT_USEC);
                        }
                    } else {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        encoderThread.start();
    }

需要把Camera的NV21的预览数据和Camera2的YV12预览数据都转换成了NV12格式的数据。

H.264编码必须要用NV12,所以我们拿到预览数据要做格式转换 .

4.2.3 获取预览数据

开启摄像头后,获取预览数据,参考之前的实现了Camera有回掉很简单(前面有写Camera 2的使用),Camera2通过ImageRender来获取。

private static final int STATE_PREVIEW = 0;
    private static final int STATE_RECORD = 1;
    private int mState = STATE_PREVIEW;
    private AvcEncoder mAvcEncoder;
    private int mFrameRate = 30;

    private void setupImageReader() {
        //2代表ImageReader中最多可以获取两帧图像流
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),mPreviewSize.getHeight(), ImageFormat.YV12,1);
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Log.e(TAG, "onImageAvailable: "+Thread.currentThread().getName() );
                //这里一定要调用reader.acquireNextImage()和img.close方法否则不会一直回掉了
                Image img = reader.acquireNextImage();
                switch (mState){
                    case STATE_PREVIEW:
                        Log.e(TAG, "mState: STATE_PREVIEW");
                        if (mAvcEncoder != null) {
                            mAvcEncoder.stopThread();
                            mAvcEncoder = null;
                            Toast.makeText(mContext,"停止录制视频成功",Toast.LENGTH_SHORT).show();
                        }
                        break;
                    case STATE_RECORD:
                        Log.e(TAG, "mState: STATE_RECORD");
                        Image.Plane[] planes = img.getPlanes();
                        byte[] dataYUV = null;
                        if (planes.length >= 3) {
                            ByteBuffer bufferY = planes[0].getBuffer();
                            ByteBuffer bufferU = planes[1].getBuffer();
                            ByteBuffer bufferV = planes[2].getBuffer();
                            int lengthY = bufferY.remaining();
                            int lengthU = bufferU.remaining();
                            int lengthV = bufferV.remaining();
                            dataYUV = new byte[lengthY + lengthU + lengthV];
                            bufferY.get(dataYUV, 0, lengthY);
                            bufferU.get(dataYUV, lengthY, lengthU);
                            bufferV.get(dataYUV, lengthY + lengthU, lengthV);
                        }

                        if (mAvcEncoder == null) {
                            mAvcEncoder = new AvcEncoder(mPreviewSize.getWidth(),
                                    mPreviewSize.getHeight(), mFrameRate,
                                    getOutputMediaFile(MEDIA_TYPE_VIDEO), false);
                            mAvcEncoder.startEncoderThread();
                            Toast.makeText(mContext, "开始录制视频成功", Toast.LENGTH_SHORT).show();
                        }
                        mAvcEncoder.putYUVData(dataYUV);
                        break;
                        default:
                            break;
                }
                img.close();
            }
        },mBackgroundHandler);
    }

源码地址:https://github.com/Xiaoben336/MediaCodecDecodeH264Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 视频压缩编码的目标1)保证压缩比例2)保证恢复的质量3)易实现,低成本,可靠性 压缩的出发点(可行性)1)时间相关...
    rogerwu1228阅读 4,130评论 0 11
  • 视频格式封装——H264 转载自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip阅读 2,362评论 0 1
  • 每日一图。 图(单反)/少帅 一曲江水向东, 一见倾心, 几度夕阳余情, 几许来生? 文/少帅
    J少帅阅读 193评论 0 0
  • 在我生命的前十八年里,我从未如此想念过这个小镇。这个南方小镇,沿河而盛,傍山而安,一年四季都是微风绕指。 我曾在穿...
    陈先生的猫阅读 528评论 0 0
  • 没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦没事啦...
    洛文添阅读 196评论 0 0