音视频处理包括
1.采集,编码,然后就是一个常见视频文件了。
2.将视频文件,进行解码,然后绘制,然后加上声音,就是视频播放了。
3.视频播放方案:使用VideoView(也是MediaPlayer+SurfaceView),接口较死,支持的格式少; 使用MediaPlayer+SurfaceView,自己封装; 使用FFMPEG+SurfaceView。
4.MediaPlayer使用的硬解码,就是MediaCodec,而FFMPEG使用的软解。
5.FFMEPG除了解码外还是有其他很多功能的,在多媒体处理方面性能和功能都很丰富强大。
6.很多小公司的直播或者短视频的应用开发是直接使用第三方的sdk,参考这篇文章:
http://www.360doc.com/content/16/0711/16/597197_574733203.shtml
视频解码出来的格式:YUV。非RGB的形式,是另外一种比RGB更节省空间的一种格式,通过采样率控制YUV文件的大小,如果要最清晰的话,那么还是跟RGB一样大。但是很多时候如果采样率不是太多的话,人眼看不出差别。采样率是对一个宏像素而言的,一个宏像素就是把几个相邻的像素点看做一个显示单元。然后采样率就是在一个宏像素中采样多少个像素点的Y,U,V,是可以分开的,也就是可以这样定这个采样率,假如一个宏像素由同一行(也可以是列或者一个正方形内)的四个相邻像素点组成,采样率为取每个像素点的Y分量和U0,V0,U4,V0,就是只取第一个像素点和第四个像素点的UV分量。
参考:https://baike.baidu.com/item/YUV/3430784?fr=aladdin
https://blog.youkuaiyun.com/lichen18848950451/article/details/70799663
软解码优势:具有更好的适应性,软件解码主要是会占用CUP的运行,软解不考虑社备的硬件解码支持情况,有CPU就可以使用了,但是占用了更多的CUP那就意味着很耗费性能,很耗电,在设备电量充足的情况下,或者设备硬件解码支持不足的情况下使用软件解码更加好!
硬件码优势:更加省电,适合长时间的移动端视频播放器和直播,手机电池有限的情况下,使用硬件解码会更加好。减少CPU的占用,可以把CUP让给别的线程使用,有利于手机的流畅度。
基于Android移植ffmpeg的意义在于:
1.解决了Android媒体框架OpenCore的多媒体支持不足,虽然说Android平台的软解功耗大,但是从PC机的发展历史看,Android的视频处理以后也会走以硬解为主,软解为辅的路线。
2. 解决Android平台直播的问题,虽然Android支持RTSP/RTP的直播方案,但是这种方案主要是普遍用在电信设备上,基于互联网的海量视频服 务提供者还是以http live streaming方案为主,测试时可以用ffmpeg将直播流打包成分段的ts流(如10秒钟),然后组织成m3u8文件实现完整的直播方案,而且互联 网的直播内容还有很多是基于mms协议的,视频格式是wmv,要聚集这些内容都是离不开ffmpeg软解的。
https://www.cnblogs.com/sode/archive/2013/01/31/2886495.html
视频解码的一般步骤:
1.注册所有组件:调用register_all
av_register_all();
2.获取上下文avFormatContext
AVFormatContext *avFormatContext = avformat_alloc_context();
3.使用avFormatContext利用path string打开视频地址并获取里面的内容(解封装)
avformat_open_input(&avFormatContext, inputPath, NULL, NULL)
4.找视频文件中的流,视频文件中一般包含音频流 视频流 字幕流
avformat_find_stream_info(avFormatContext, NULL)
5.标记视频流,记住视频流对应流数组中的index
int video_index=-1;
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
if (avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
//如果是视频流,标记一哈
video_index = i;
}
}
6.获取解码器上下文
AVCodecContext *avCodecContext = avFormatContext->streams[video_index]->codec;
7.获取解码器
AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);
8.打开解码器
avcodec_open2(avCodecContext, avCodec, NULL)
9.申请AVPacket,放置解码前的视频文件
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
av_init_packet(packet);//使用默认值初始化AVPacket结构体的某些字段
10.申请AVFrame,放置解码后的一桢。
AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧
11.解码出来的原始帧一般还会再做一次转换,变成某种格式的数据。如转换成YUV格式的帧,转换成rbga格式的帧。
放置这些转换后的帧数据还需要一个AVFrame,这个AVFrame一般还会绑定一个缓冲区。
假如是转换成rgba格式的帧:
AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧
//缓存区
uint8_t *out_buffer= (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA,
avCodecContext->width,avCodecContext->height));
//与缓存区相关联,设置rgb_frame缓存区
avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,avCodecContext-> width,avCodecContext-> height);
注意:转换成什么格式是数据,上面的代码对应的参数需要指明是什么格式的,像上面的就是AV_PIX_FMT_RGBA,不同格式的数据,转换后的占空间大小和一些放置顺序都不一样的。如果转换成YUV格式的帧,那么就对应的参数值是AV_PIX_FMT_YUV420P,也可以不是AV_PIX_FMT_YUV420P,有其他格式的YUV,具体可以看上面的网址对应的内容。
12.开始将解码出来的原始帧转换了,用到的是SwsContext,所以需要获取一个SwsContext,获取时主要AV_PIX_FMT_RGBA参数,要转换成什么格式,就要传对应的参数值
SwsContext* swsContext = sws_getContext(avCodecContext->width,avCodecContext->height,avCodecContext-> pix_fmt,avCodecContext->width,avCodecContext->height,AV_PIX_FMT_RGBA, SWS_BICUBIC,NULL,NULL,NULL);
13.开始解码过程,就是从视频文件数据把一桢中流数据数据读到AVPacket中(一桢视频包含了视频流,音频流,字幕流,所以一桢视频应该需要循环3次才能读完),如果packet中的流是视频流,就使用avcodec_decode_video2(avCodecContext, frame, &frameCount, packet);解码,解码后把解码的帧放到AVFrame中,这样就完成了整个解码的过程。至于最后把解码后的数据怎么用,就看具体的了。像下面的代码就是把解码后的rbga帧直接绘制到surface上显示。而转成YUV数据后,一般会写到文件中。
while (av_read_frame(avFormatContext, packet) >= 0) {
LOGE("解码 %d",packet->stream_index)
LOGE("VINDEX %d",video_index)
if(packet->stream_index==video_index){
LOGE("解码 hhhhh")
//如果是视频流
//解码
avcodec_decode_video2(avCodecContext, frame, &frameCount, packet);
LOGE("解码中.... %d",frameCount)
if (frameCount) {
LOGE("转换并绘制")
//说明有内容
//绘制之前配置nativewindow
ANativeWindow_setBuffersGeometry(nativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888);
//上锁
ANativeWindow_lock(nativeWindow, &native_outBuffer, NULL);
//转换为rgb格式
sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,
frame->height,rgb_frame->data,
rgb_frame->linesize);
// rgb_frame是有画面数据
uint8_t *dst= (uint8_t *) native_outBuffer.bits;
// 拿到一行有多少个字节 RGBA
int destStride=native_outBuffer.stride*4;
//像素数据的首地址
uint8_t * src= rgb_frame->data[0];
// 实际内存一行数量
int srcStride = rgb_frame->linesize[0];
//int i=0;
for (int i = 0; i < avCodecContext->height; ++i) {
// memcpy(void *dest, const void *src, size_t n)
//将rgb_frame中每一行的数据复制给nativewindow
memcpy(dst + i * destStride, src + i * srcStride, srcStride);
}
//解锁
ANativeWindow_unlockAndPost(nativeWindow);
usleep(1000 * 16);
}
}
av_free_packet(packet);
}
下面是转换成YUV格式的循环:
while(av_read_frame(pFormatCtx, packet)>=0){
if(packet->stream_index==videoindex){
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(ret < 0){
LOGE("Decode Error.\n");
return -1;
}
if(got_picture){
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
y_size=pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
//Output info
char pictype_str[10]={0};
switch(pFrame->pict_type){
case AV_PICTURE_TYPE_I:sprintf(pictype_str,"I");break;
case AV_PICTURE_TYPE_P:sprintf(pictype_str,"P");break;
case AV_PICTURE_TYPE_B:sprintf(pictype_str,"B");break;
default:sprintf(pictype_str,"Other");break;
}
LOGI("Frame Index: %5d. Type:%s",frame_cnt,pictype_str);
frame_cnt++;
}
}
av_free_packet(packet);
}
//flush decoder
//FIX: Flush Frames remained in Codec
while (1) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
break;
if (!got_picture)
break;
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
int y_size=pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
//Output info
char pictype_str[10]={0};
switch(pFrame->pict_type){
case AV_PICTURE_TYPE_I:sprintf(pictype_str,"I");break;
case AV_PICTURE_TYPE_P:sprintf(pictype_str,"P");break;
case AV_PICTURE_TYPE_B:sprintf(pictype_str,"B");break;
default:sprintf(pictype_str,"Other");break;
}
LOGI("Frame Index: %5d. Type:%s",frame_cnt,pictype_str);
frame_cnt++;
}
最后要释放这些数据结构的空间:
av_free_packet(packet);
//释放
ANativeWindow_release(nativeWindow);
av_frame_free(&frame);
av_frame_free(&rgb_frame);
avcodec_close(avCodecContext);
avformat_free_context(avFormatContext);
env->ReleaseStringUTFChars(inputStr_, inputPath);
转换成rbga并写到surface中,参考的博文:
https://www.jianshu.com/p/c7de148e951c