FFMPEG学习小结1

墨蓝 2022-05-22 21:08 353阅读 0赞

音视频处理包括

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

70

视频解码出来的格式: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.csdn.net/lichen18848950451/article/details/70799663

软解码优势:具有更好的适应性,软件解码主要是会占用CUP的运行,软解不考虑社备的硬件解码支持情况,有CPU就可以使用了,但是占用了更多的CUP那就意味着很耗费性能,很耗电,在设备电量充足的情况下,或者设备硬件解码支持不足的情况下使用软件解码更加好!

硬件码优势:更加省电,适合长时间的移动端视频播放器和直播,手机电池有限的情况下,使用硬件解码会更加好。减少CPU的占用,可以把CUP让给别的线程使用,有利于手机的流畅度。

基于Android移植ffmpeg的意义在于:

1.解决了Android媒体框架OpenCore的多媒体支持不足,虽然说Android平台的软解功耗大,但是从PC机的发展历史看,Android的视频处理以后也会走以硬解为主,软解为辅的路线。

  1. 解决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数据后,一般会写到文件中。

  1. while (av_read_frame(avFormatContext, packet) >= 0) {
  2. LOGE("解码 %d",packet->stream_index)
  3. LOGE("VINDEX %d",video_index)
  4. if(packet->stream_index==video_index){
  5. LOGE("解码 hhhhh")
  6. //如果是视频流
  7. //解码
  8. avcodec_decode_video2(avCodecContext, frame, &frameCount, packet);
  9. LOGE("解码中.... %d",frameCount)
  10. if (frameCount) {
  11. LOGE("转换并绘制")
  12. //说明有内容
  13. //绘制之前配置nativewindow
  14. ANativeWindow_setBuffersGeometry(nativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888);
  15. //上锁
  16. ANativeWindow_lock(nativeWindow, &native_outBuffer, NULL);
  17. //转换为rgb格式
  18. sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,
  19. frame->height,rgb_frame->data,
  20. rgb_frame->linesize);
  21. // rgb_frame是有画面数据
  22. uint8_t *dst= (uint8_t *) native_outBuffer.bits;
  23. // 拿到一行有多少个字节 RGBA
  24. int destStride=native_outBuffer.stride*4;
  25. //像素数据的首地址
  26. uint8_t * src= rgb_frame->data[0];
  27. // 实际内存一行数量
  28. int srcStride = rgb_frame->linesize[0];
  29. //int i=0;
  30. for (int i = 0; i < avCodecContext->height; ++i) {
  31. // memcpy(void *dest, const void *src, size_t n)
  32. //将rgb_frame中每一行的数据复制给nativewindow
  33. memcpy(dst + i * destStride, src + i * srcStride, srcStride);
  34. }
  35. //解锁
  36. ANativeWindow_unlockAndPost(nativeWindow);
  37. usleep(1000 * 16);
  38. }
  39. }
  40. av_free_packet(packet);
  41. }

下面是转换成YUV格式的循环:

  1. while(av_read_frame(pFormatCtx, packet)>=0){
  2. if(packet->stream_index==videoindex){
  3. ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  4. if(ret < 0){
  5. LOGE("Decode Error.\n");
  6. return -1;
  7. }
  8. if(got_picture){
  9. sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
  10. pFrameYUV->data, pFrameYUV->linesize);
  11. y_size=pCodecCtx->width*pCodecCtx->height;
  12. fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
  13. fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
  14. fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
  15. //Output info
  16. char pictype_str[10]={0};
  17. switch(pFrame->pict_type){
  18. case AV_PICTURE_TYPE_I:sprintf(pictype_str,"I");break;
  19. case AV_PICTURE_TYPE_P:sprintf(pictype_str,"P");break;
  20. case AV_PICTURE_TYPE_B:sprintf(pictype_str,"B");break;
  21. default:sprintf(pictype_str,"Other");break;
  22. }
  23. LOGI("Frame Index: %5d. Type:%s",frame_cnt,pictype_str);
  24. frame_cnt++;
  25. }
  26. }
  27. av_free_packet(packet);
  28. }
  29. //flush decoder
  30. //FIX: Flush Frames remained in Codec
  31. while (1) {
  32. ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  33. if (ret < 0)
  34. break;
  35. if (!got_picture)
  36. break;
  37. sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
  38. pFrameYUV->data, pFrameYUV->linesize);
  39. int y_size=pCodecCtx->width*pCodecCtx->height;
  40. fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
  41. fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
  42. fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
  43. //Output info
  44. char pictype_str[10]={0};
  45. switch(pFrame->pict_type){
  46. case AV_PICTURE_TYPE_I:sprintf(pictype_str,"I");break;
  47. case AV_PICTURE_TYPE_P:sprintf(pictype_str,"P");break;
  48. case AV_PICTURE_TYPE_B:sprintf(pictype_str,"B");break;
  49. default:sprintf(pictype_str,"Other");break;
  50. }
  51. LOGI("Frame Index: %5d. Type:%s",frame_cnt,pictype_str);
  52. frame_cnt++;
  53. }

最后要释放这些数据结构的空间:

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

发表评论

表情:
评论列表 (有 0 条评论,353人围观)

还没有评论,来说两句吧...

相关阅读

    相关 1.0

    // 平常未了解的知识 在eclipse的命令行参数中 \ 会被操作系统当作通配符,\号会把Eclipse当前项目的所有文件与文件夹当作参数传进来,无论是'\',还是"\"都

    相关 C指针1

                                                C指针小结1      我认为C语言的核心就是指针和对内存的操作,而操作内存就是由指针

    相关 FFMPEG学习1

    音视频处理包括 1.采集,编码,然后就是一个常见视频文件了。 2.将视频文件,进行解码,然后绘制,然后加上声音,就是视频播放了。 3.视频播放方案:使用VideoView