记录一下之前项目的实际使用过程。
将按照Java层------>JNI接口------>JNI代码中使用FFmpeg解码。
首先Java层:

public class CodecWrapper {//加载FFmpeg的动态so库static {System.loadLibrary("codec");System.loadLibrary("avutil-55");System.loadLibrary("swresample-2");System.loadLibrary("avcodec-57");System.loadLibrary("avformat-57");System.loadLibrary("swscale-4");System.loadLibrary("postproc-54");System.loadLibrary("avfilter-6");System.loadLibrary("avdevice-57");}//进行FFmpeg解码器部分的初始化public native long get_codec();//传入数据,解码。没来一帧数据都会调用这个方法。//surface是解码之后的数据显示的地方,作为一个对象传入到JNI。public native void decode_stream(byte[] frame, int length, long decoder, Surface surface);//解码完成,释放资源public native void release_codec(long decoder);//解码之后,图像数据将被添加到Surface的数据缓冲区中,完成显示。它通过SurfaceView对象可以获得。private Surface mSurface;private long mDecoderHandle;public CodecWrapper(Surface surface){mSurface = surface;init();}public void init(){mDecoderHandle = get_codec();}public void decodeStream(byte[] frame, int length){decode_stream(frame, length, mDecoderHandle, mSurface);}public void release(){release_codec(mDecoderHandle);}@Overrideprotected void finalize() throws Throwable {try {release();} finally {try {super.finalize();} catch (Throwable e) {e.printStackTrace();}}}//this method was called by jni when one frame was decode ok/*public void onFrameDecode(byte[] data, int width, int height){getOneFrame(data, width, height);}abstract void getOneFrame(byte[] data, int width, int height);*/public void onFrameDecode(int[] data, int width, int height){getOneFrame(data, width, height);}abstract void getOneFrame(int[] data, int width, int height);
}

**首先,**需要调用CodecWrapper的构造方法,传入Surface对象,然后调用native方法get_codec,对FFmpeg的解码器进行初始化。
**之后,**每来一帧数据,就会调用decode_stream方法进行真正的数据解码。
**解码完成之后,**调用release_codec方法释放JNI层的资源,防止出现内存泄露。
接下来看JNI接口的部分代码:
com_xxx_xxx_CodecWrapper.h

#include <jni.h>
/** Method: 初始化*/
JNIEXPORT jint JNICALL Java_com_xxx_CodecWrapper_get_codec(JNIEnv *, jobject);/** Method: 解码* Signature: ([BIILcom/xxxx/CodecWrapper;)V*/
JNIEXPORT void JNICALL Java_com_xxx_CodecWrapper_decode_stream(JNIEnv *, jobject, jbyteArray, jint, jint, jobject);/** Method:    释放资源* Signature: (I)V*/
JNIEXPORT void JNICALL Java_com_xxx_CodecWrapper_release_1codec(JNIEnv *, jobject, jint);

com_xxx_xxx_CodecWrapper.cpp

#include <jni.h>
#include "../decoder.h"
#include "../yuv_2_rgb.h"
#include "../android/android_native_window.h"
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "../decoder.h"
#include "../timeutil.h"
#include <libavutil/imgutils.h>enum AVPixelFormat pixelFormat = AV_PIX_FMT_RGB565LE;
int native_pix_format = PIXEL_FORMAT_RGB_565;
//对当前JVM环境的封装,记录JNIEnv、Java层的对象(Java中哪个对象调用的当前的JNI方法)、Surface对象
typedef struct _EnvPackage{JNIEnv *env;jobject *obj;jobject *surface;
} EnvPackage;JNIEXPORT long JNICALL Java_com_xxxx_CodecWrapper_get_1codec(JNIEnv *env, jobject obj){decoder *p = new decoder();p->initialize(pixelFormat);return p;
}JNIEXPORT void JNICALL Java_com_xxxx_CodecWrapper_decode_1stream(JNIEnv *env, jobject obj, jbyteArray jdata, jint length, jlong this_obj_long, jobject surface){decoder *this_obj = this_obj_long;//将Java层的byte数组,转成JNI中的jbyte指针数组jbyte *cdata = env->GetByteArrayElements(jdata, JNI_FALSE);jbyte *cdata_rec = cdata;if(cdata != NULL) {EnvPackage package;package.env = env;package.obj = &obj;package.surface = &surface;//解码显示this_obj->decodeFrame(cdata, length, handle_data, &package,this_obj);}else{LOGE("stream data is NULL");}//释放资源env->ReleaseByteArrayElements(jdata, cdata_rec, 0);
}JNIEXPORT void JNICALL Java_com_xxxx_CodecWrapper_release_1codec(JNIEnv *env, jobject obj, long this_obj_long){decoder *this_obj = this_obj_long;this_obj->close();delete this_obj;
}//为了计算解码帧率
int frame_count = 0;//使用FFmpeg解码之后生成yuv420p数据后,会调用这个方法
//这里的pFrame就包含着yuv420p数据
void handle_data(AVFrame *pFrame, void *param, void *ctx,decoder *this_obj){RenderParam *renderParam = (RenderParam *)param;//为了显示,还需要转成ARGB数据AVFrame   *rgbFrame = yuv420p_2_argb(pFrame, renderParam->swsContext, renderParam->avCodecContext, pixelFormat);//AV_PIX_FMT_RGB565LEif (this_obj->aNativeWindow == NULL){EnvPackage *envPackage = (EnvPackage *)ctx;//通过ANativeWindow_fromSurface方法,将从Java层传下来的Surface对象转成NativeWindow类型的对象this_obj->aNativeWindow = ANativeWindow_fromSurface(envPackage->env, *(envPackage->surface));}VoutInfo voutInfo;voutInfo.buffer = rgbFrame->data[0];voutInfo.buffer_width = rgbFrame->width;voutInfo.buffer_height = rgbFrame->height;voutInfo.pix_format = native_pix_format;
//显示android_native_window_display(this_obj->aNativeWindow, &voutInfo);LOGE("display frame ", frame_count++);fps();//计算帧率//显示完了之后需要释放这个NativeWindow资源对象ANativeWindow_release(aNativeWindow);//释放图像数据av_free(rgbFrame->data[0]);av_free(rgbFrame);
}

OK,上面就是JNI接口层的代码,里面只是简单的更下层代码的封装,接下来看JNI层代码是如何实现解码并显示的:
decoder.h

#include <inttypes.h>
#include <stdio.h>
#include <iostream>
#include <stdint.h>
#include <stdlib.h>
#include <sstream>
#include <string>
#include <cstring>
#include <fstream>
#include "../log.h"
#include <pthread.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "ffmpeg/libavcodec/avcodec.h"
#include "ffmpeg/libavformat/avformat.h"
#include "ffmpeg/libswscale/swscale.h"
#include <queue>//用于接收从Java层传过来的H264数据,
//这里是写死的,因为H264每一帧大小都不一样,1024 * 600是大于当时项目一帧H264数据大小,要不会包内存溢出的异常
#define INBUF_SIZE 1024 * 600
//#define INBUF_SIZE 8192typedef struct _RenderParam{SwsContext *swsContext;AVCodecContext *avCodecContext;
}RenderParam;
//记录H264 NALU信息
typedef struct _NalInfo{uint8_t forbidden_zero_bit;uint8_t nal_ref_idc;uint8_t nal_unit_type;
} NalInfo;
//decoder 类
class decoder {private:int frame_count;//记录解码成功并显示完成的帧数AVFrame *frame;uint8_t inbuf[INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];AVPacket avpkt;//它用来存放要解析的数据AVCodecParserContext *parser;//从传进来的数据中解析出H264帧数据,传进来的数据有可能是多个H264数据,需要一个一个解析出来AVCodec *codec;//用于音视频解码,用于创建AVCodecContext 对象AVCodecContext *codecContext;//用于音视频解码SwsContext *img_convert_ctx;//用于执行图像数据格式的转化和缩放enum AVPixelFormat pixelFormat;//图像数据格式//rgb frame cacheAVFrame   *pFrameRGB;//缓存最后转成的RGB数据
public:
//    pthread_mutex_t mutex1;RenderParam *renderParam = NULL;ANativeWindow *aNativeWindow;//由Surface转成的JNI层中的界面窗口对象decoder();void initialize(enum AVPixelFormat format);//初始化//解码的方法int decodeFrame(const char *data, int length, void (*handle_data)(AVFrame *pFrame, void *param, void *ctx,decoder *object), void *ctx,decoder *object);void close();void setFrameRGB(AVFrame *frame);int handleH264Header(uint8_t* ptr, NalInfo *nalInfo);AVFrame *getFrameRGB();
};

decoder.cpp

#include "decoder.h"
#include "timeutil.h"
//相当于decoder的构造方法,构造方法中进行一些成员变量的初始化工作。
decoder::decoder() :codec(NULL), codecContext(NULL), frame_count(0),  frame(NULL), parser(NULL) {}
//初始化工作
void decoder::initialize(enum AVPixelFormat format) {/* register all the codecs 进行必要的初始化注册操作,比如解码器、编码器、解析器、硬件加速器等。只有调用了这个方法,才能使用解码器。它几乎是所有FFmpeg程序第一个调用的方法。 */avcodec_register_all();//创建一个AVPacket对象,它用来存放要解析的数据。 av_init_packet(&avpkt);//为RenderParam 对象分配内存空间renderParam =  (RenderParam *)malloc(sizeof(RenderParam));/* find the x264 video decoder 获取H264解码器*/codec = avcodec_find_decoder(AV_CODEC_ID_H264);if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}
//视音频流对应的结构体,用于视音频编解码。创建AVCodecContext结构体对象,参数为AVCodec对象。 codecContext = avcodec_alloc_context3(codec);if (!codecContext) {fprintf(stderr, "Could not allocate video codec context\n");exit(1);}/* we do not send complete frames */if (codec->capabilities & CODEC_CAP_TRUNCATED)codecContext->flags |= CODEC_FLAG_TRUNCATED;/* open it 使用AVCodec初始化AVCodecContext,指向相应的数据。 打开相应的解码器*/if (avcodec_open2(codecContext, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}
//给AVFrame分配内存。 frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}
//初始化H264解析器parser = av_parser_init(AV_CODEC_ID_H264);if (!parser) {std::cout << "cannot create parser" << std::endl;exit(1);}printf(" decoder init ..........\n");frame_count = 0;//初始化SwsContextimg_convert_ctx =NULL;//初始化存放RGB类型数据的对象pFrameRGB = NULL;//pixelFormat = AV_PIX_FMT_BGRA;//pixelFormat = AV_PIX_FMT_RGB565LE;//pixelFormat = AV_PIX_FMT_BGR24;pixelFormat = format;
}
//在解码的部分加锁
static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER;int decoder::decodeFrame(const char *data, int length, void (*handle_data)(AVFrame *pFrame, void *param, void *ctx,decoder *this_obj), void *ctx,decoder *this_obj) {pthread_mutex_lock (&buffer_lock);int cur_size = length;int ret = 0;LOGI("decodeFrame 1 ,length is %d: ",length);//将inbuf从0到length部分全部置0,length表示传入H24数据的长度memset(inbuf , 0, length);//将inbuf从length到length+FF_INPUT_BUFFER_PADDING_SIZE部分全部置0,//之所以有这个FF_INPUT_BUFFER_PADDING_SIZE,是因为解码器需要每一段H264数据之间需要有一个Paddingmemset(inbuf + length, 0, FF_INPUT_BUFFER_PADDING_SIZE);//将data复制到inbuf中memcpy(inbuf, data, length);data = NULL;
//指向inbuf的开始内存地址const uint8_t *cur_ptr = inbuf;// Parse input stream to check if there is a valid frame.//循环while(cur_size >0){//从当前H264数据中解析出一帧数据,parsedLength 就是这一帧数据的长度int parsedLength = av_parser_parse2(parser, codecContext, &avpkt.data,&avpkt.size, (const uint8_t*)cur_ptr , cur_size, AV_NOPTS_VALUE,AV_NOPTS_VALUE, AV_NOPTS_VALUE);cur_ptr += parsedLength;//cur_ptr 指向下一帧数据cur_size -= parsedLength;//当前待解析的H264数据所剩余的长度LOGI("decodeFrame 3-1 cur_size == %d parsedLength == %d,avpkt.size=%d", cur_size, parsedLength,avpkt.size);// 67 sps// 68 pps// 65 i// 61 p//LOGE("parsedLength %d    %x %x %x %x %x %x %x %x", parsedLength, cur_ptr[0], cur_ptr[1], cur_ptr[2], cur_ptr[3], cur_ptr[4], cur_ptr[5], cur_ptr[6], cur_ptr[7]);//LOGE("parsedLength %d    %x %x %x %x %x %x %x %x", parsedLength, *(cur_ptr-parsedLength), *(cur_ptr-parsedLength+1), *(cur_ptr-parsedLength+2), *(cur_ptr-parsedLength+3), *(cur_ptr-parsedLength+4), *(cur_ptr-parsedLength+5), *(cur_ptr-parsedLength+6), *(cur_ptr-parsedLength+7));NalInfo nalInfo;//解析H264 NALU Header头部信息ret = handleH264Header(cur_ptr-parsedLength, &nalInfo);if(ret == 0){}if (!avpkt.size) {//如果没有数据LOGI("avpkt.size = %d,continue",avpkt.size);continue;} else {int len;//解码成功的长度int got_frame;//该值为0表明没有图像可以解码,否则表明有图像可以解码//codecContext:编解码上下文环境,定义了编解码操作的一些细节;//frame:输出参数,解码之后的数据//got_frame//avpkt:输入参数,包含待解码的H264数据len = avcodec_decode_video2(codecContext, frame, &got_frame, &avpkt);LOGI("decodeFrame 4, decode len is %d,got_frame = %d", len, got_frame);if (len < 0) {fprintf(stderr, "Error while decoding frame %d\n", frame_count);continue;}if (got_frame) {//如果解码成功frame_count++;LOGE("frame %d", frame_count);if (img_convert_ctx == NULL) {//创建一个SwsContext 对象,用于解码之后的数据的格式转化和缩放工作img_convert_ctx = sws_getContext(codecContext->width, codecContext->height,codecContext->pix_fmt, codecContext->width,codecContext->height,pixelFormat, SWS_BICUBIC, NULL, NULL,NULL);//填充参数值renderParam->swsContext = img_convert_ctx;renderParam->avCodecContext = codecContext;}if (img_convert_ctx != NULL) {//就是com_xxx_xxx_CodecWrapper.cpp中的handle_data方法handle_data(frame, renderParam, ctx, this_obj);}}//解锁pthread_mutex_unlock (&buffer_lock);return length;
}
//解码之后,做一些资源释放工作
void decoder::close() {av_free_packet(&avpkt);avpkt.data = NULL;avpkt.size = 0;if (parser) {av_parser_close(parser);parser = NULL;}if(renderParam){free(renderParam);renderParam = NULL;}if(pFrameRGB){delete pFrameRGB;pFrameRGB = NULL;}avcodec_close(codecContext);av_free(codecContext);av_frame_free(&frame);if(img_convert_ctx!=NULL){sws_freeContext(img_convert_ctx);img_convert_ctx = NULL;}if (aNativeWindow != NULL){//窗口资源ANativeWindow_release(aNativeWindow);}printf(" decoder close ..........\n");
}AVFrame * decoder::getFrameRGB() {return pFrameRGB;
}
//解析H264数据帧NALU的头部
int decoder::handleH264Header(uint8_t* ptr, NalInfo *nalInfo){LOGI("handleH264Header 1 ")int startIndex = 0;uint32_t *checkPtr = (uint32_t *)ptr;if(*checkPtr == 0x01000000){  // 00 00 00 01startIndex = 4;}else if(*(checkPtr) == 0 && *(checkPtr+1)&0x01000000){  // 00 00 00 00 01startIndex = 5;}if(!startIndex){//没有找到H264头部return -1;}else{ptr = ptr + startIndex;nalInfo->nal_unit_type = 0x1f & *ptr;if(nalInfo->nal_unit_type == 5 || nalInfo->nal_unit_type == 7 || nalInfo->nal_unit_type == 8 || nalInfo->nal_unit_type == 2){  //I frameLOGE("I frame");}else if(nalInfo->nal_unit_type == 1){LOGE("P frame");}}LOGI("handleH264Header 2 ")return 0;
}void decoder::setFrameRGB(AVFrame *frame) {pFrameRGB = frame;
}

yuv_2_rgb.cpp

//由avcodec_decode_video2解码之后的数据时YUV420P类型的数据,这里转成ARGB数据
AVFrame *yuv420p_2_argb(AVFrame *frame, SwsContext *swsContext, AVCodecContext *avCodecContext, enum AVPixelFormat format){AVFrame  *pFrameRGB = NULL;uint8_t  *out_bufferRGB = NULL;//为RGB数据分配内存pFrameRGB = av_frame_alloc();pFrameRGB->width = frame->width;pFrameRGB->height = frame->height;int size = avpicture_get_size(format, avCodecContext->width, avCodecContext->height);out_bufferRGB = av_malloc(size * sizeof(uint8_t));//给pFrameRGB帧加上分配的内存;  //AV_PIX_FMT_ARGBavpicture_fill((AVPicture *)pFrameRGB, out_bufferRGB, format, avCodecContext->width, avCodecContext->height);//YUV to RGBsws_scale(swsContext, frame->data, frame->linesize, 0, avCodecContext->height, pFrameRGB->data, pFrameRGB->linesize);return pFrameRGB;
}

android_native_window.h

#include <android/native_window.h>
#include <android/native_window_jni.h>
enum {PIXEL_FORMAT_RGBA_8888 = 1,PIXEL_FORMAT_RGBX_8888 = 2,PIXEL_FORMAT_RGB_565 = 3
};typedef struct _VoutInfo{/**WINDOW_FORMAT_RGBA_8888          = 1,WINDOW_FORMAT_RGBX_8888          = 2,WINDOW_FORMAT_RGB_565            = 4,*/uint32_t pix_format;uint32_t buffer_width;uint32_t buffer_height;uint8_t *buffer;
} VoutInfo;typedef struct _VoutRender{uint32_t pix_format;uint32_t window_format;//渲染到窗口上void (*render)(ANativeWindow_Buffer *nwBuffer, VoutInfo *voutInfo);
}VoutRender;
//显示
void android_native_window_display(ANativeWindow *aNativeWindow, VoutInfo *voutInfo);

android_native_window.cpp

#include "android_native_window.h"
//应该是解码渲染的最后一步。最终渲染到屏幕上面就是会走到这里
//当我们解码完毕后,需要把rgb帧渲染到画布上,这个过程参考了ijkplayer的实现,将surface作为一个参数传入jni层,
//拿到surface的缓冲区后将生成的rgb数据直接copy到这个缓冲区即可完成显示。
void render_on_rgb(ANativeWindow_Buffer *nwBuffer, VoutInfo *voutInfo, int bpp){int stride = nwBuffer->stride;int dst_width = nwBuffer->width;int dst_height = nwBuffer->height;LOGE("ANativeWindow stride %d width %d height %d", stride, dst_width, dst_height);int line = 0;int src_line_size = voutInfo->buffer_width * bpp / 8;int dst_line_size = stride * bpp / 8;int min_height = dst_height < voutInfo->buffer_height ? dst_height : voutInfo->buffer_height;if(src_line_size == dst_line_size) {//void *memcpy(void*dest, const void *src, size_t n);    //将rgb数据拷贝给suface的缓冲区,完成现实memcpy((__uint8_t *) nwBuffer->bits, (__uint8_t *) voutInfo->buffer, src_line_size * min_height);}else{//直接copy/*for(int i=0; i<height; i++){memcpy((__uint8_t *) (nwBuffer.bits + line), (__uint8_t *)(rgbFrame->data[0]+ width*i * 2), width * 2);line += stride * 2;}*///使用ffmpeg的函数 实现相同功能av_image_copy_plane(nwBuffer->bits, dst_line_size, voutInfo->buffer, src_line_size, src_line_size, min_height);}
}void render_on_rgb8888(ANativeWindow_Buffer *nwBuffer, VoutInfo *voutInfo){render_on_rgb(nwBuffer, voutInfo, 32);
}void render_on_rgb565(ANativeWindow_Buffer *nwBuffer, VoutInfo *voutInfo){render_on_rgb(nwBuffer, voutInfo, 16);
}static VoutRender g_pixformat_map[] = {{PIXEL_FORMAT_RGBA_8888, WINDOW_FORMAT_RGBA_8888,render_on_rgb8888},{PIXEL_FORMAT_RGBX_8888, WINDOW_FORMAT_RGBX_8888, render_on_rgb8888},{PIXEL_FORMAT_RGB_565, WINDOW_FORMAT_RGB_565, render_on_rgb565}
};VoutRender *get_render_by_window_format(int window_format){int len = sizeof(g_pixformat_map);for(int i=0; i<len; i++){if(g_pixformat_map[i].window_format == window_format){return &g_pixformat_map[i];}}
}void android_native_window_display(ANativeWindow *aNativeWindow, VoutInfo *voutInfo){int curr_format = ANativeWindow_getFormat(aNativeWindow);//选择合适的渲染器VoutRender *render = get_render_by_window_format(curr_format);ANativeWindow_Buffer nwBuffer;//在前面ANativeWindow 对象已经创建好了//ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(envPackage->env, *(envPackage->surface));if (aNativeWindow == NULL) {LOGE("ANativeWindow_fromSurface error");return;}//scaled buffer to fit window   //用来缩放rgb帧与显示窗口的大小,使得rgb数据可以适应窗口大小int retval = ANativeWindow_setBuffersGeometry(aNativeWindow, voutInfo->buffer_width, voutInfo->buffer_height,  render->window_format);if (retval < 0) {LOGE("ANativeWindow_setBuffersGeometry: error %d", retval);return retval;}
//锁定surface if (0 != ANativeWindow_lock(aNativeWindow, &nwBuffer, 0)) {LOGE("ANativeWindow_lock error");return;}//根据前面选择好的渲染器render->render(&nwBuffer, voutInfo);
// //解锁surface并显示新的缓冲区if(0 !=ANativeWindow_unlockAndPost(aNativeWindow)){LOGE("ANativeWindow_unlockAndPost error");return;}ANativeWindow_release(aNativeWindow);
}

H264视频传输、编解码----FFmpeg软解码相关推荐

  1. iOS8系统H264视频硬件编解码说明

    iOS8系统H264视频硬件编解码说明 转载自:http://www.tallmantech.com/archives/206#more-206 公司项目原因,接触了一下视频流H264的编解码知识,之 ...

  2. ffmpeg硬解码与软解码的压测对比

    文章目录 ffmpeg硬解码与软解码的压测 一.基本知识 二.压测实验 1. 实验条件及工具说明 2. 压测脚本 3. 实验数据结果 ffmpeg硬解码与软解码的压测 一.基本知识 本文基于intel ...

  3. 手机视频硬解码和软解码的区别

    在手机评测视频播放能力的时候经常会提到"硬解码"和"软解码",但是很多人不太明白是什么意思,其实问题很简单.大家都知道PC都有CPU和GPU(显卡),在手机上也 ...

  4. 硬解码与软解码的选择

    前言 事物都有两面性,软解码和硬解码的并存,存在即合理,没有哪个最好,以后两者都会更好,而对于如何选择,根据项目需要. 在上篇<快速集成一个视频直播功能> 中提到,"确定需求后进 ...

  5. “硬解码”与“软解码”的区别

    关于"硬解码"与"软解码" 忧蓝 发布于: 2010-08-02 11:03 由于高清视频的分辨率远远高于一般格式视频,使得高清视频的码率非常高.再加上VC-1 ...

  6. iOS系统H264视频硬件编解码说明

    公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...

  7. 硬解码和软解码的区别

    我们在计算机上播放的视频文件都是经过压缩的,因为这样有利于节约存储空间:那么在播放过程,就需要进行一个反射的解压缩过程.在以前这项工作都是由CPU来完成的,对于普通分辨率的AVI.RMVB等文件,绝大 ...

  8. 关于“硬解码”与“软解码”

    由于高清视频的分辨率远远高于一般格式视频,使得高清视频的码率非常高.再加上VC-1和H.264编码的压缩率很高,解码运算的运算量很大.因此常规地直接用CPU解码(即常说的"软解") ...

  9. H264视频传输、编解码----RTP协议对H264数据帧拆包、打包、解包过程

    H264帧需要通过RTP协议进行传输,这其中就涉及到H264数据帧的封包.拆包和解包等过程. RTP协议格式 下面是 RFC 3550 中规定的 RTP 头的结构: 0 1 2 3 40 1 2 3 ...

最新文章

  1. 开机logo切换逻辑深入研究
  2. markdown 流程图js_科学网—让Markdown支持ASCII流程图和JavaScript流程图 - 李继存的博文...
  3. 2.shiro工作原理(以集成springboot为例)
  4. 【机器视觉】 stop算子
  5. Linux Ubuntu安装sogou中文输入法
  6. 算术运算符_自增自减运算符
  7. 个人成长:2021年终记
  8. 无显示器u盘安装centos_最新版 CentOS 8.1.1911 安装教程及常见问题图文详解
  9. 数字化转型知识方法系列之:数字化转型的基本认识与参考架构
  10. libv4l 库【转】
  11. java 适配器模式记载学习
  12. 组合模式——公司组织结构
  13. 【Go】使用压缩文件优化io (二)
  14. 苹果ipad怎么录屏_原来苹果手机还能这样投屏!1秒小屏变大屏!太好用了
  15. 基于QT实现的钢琴软件 (MFC大作业)
  16. 23种设计模式设计原则
  17. 【01 赖世雄英语语法:单句的语法(句子的构成)】
  18. NCCL (NVIDIA Collective Communications Library)
  19. 梦之安魂曲 minisd_科技运动中妇女的安魂曲
  20. 大数据剖析热点新闻:996、巴黎圣母院、奔驰维权为什么成为本周热搜

热门文章

  1. 华为 DHCP基本配置及概念
  2. iPhone刷门禁卡的设置方法
  3. 手机党心声:“离开手机生活”这是不可能的!
  4. 认识jQuery的Promise
  5. 3D游戏设计作业(四)
  6. 有没有被坦克大战支配过?
  7. Spring AOP动态代理
  8. 未雨绸缪:面试前为何要带一份不一定被阅的简历?
  9. 网络及路由器故障诊断基础知识
  10. DELPHI线程创建与使用