FFMPEG学习小结1

音视频处理包括

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

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值