需求:视频录制、30秒保存一个mp4文件。

一、采用MediaRecord录制

优点:
使用方便,得到就是编码和封装好的音视频文件,可以直接使用。
缺点:
无法获取原始数据,从而无法对原始数据添加一些自己的处理。最重要的是如果30秒保存一次文件,就需要关闭MediaRecord,然后再重新打开。这样一关一开就会造成帧数据丢失。

二、硬编码,通过camera、openGL、mediacodec、mediamuxer实现录制

录制流程图如下:

参考原文:https://blog.csdn.net/qq_36391075/article/details/81747190

思路:

1、通过MediaCodec创建一个用于输入的Surface
2、通过通过camera预览时的上下文EGL创建OpenGL的环境,根据上面得到的Surface创建EGLSuface。
3、通过camera预览时的绑定的纹理id,进行纹理绘制。
4、交换数据,让数据输入新Surface。使用AudioReocod进行声音的采集
5、通过Mediacodec编码为h264、AAC。
6、通过MediaMuxer进行数据封装为mp4。

对于30秒保存一次文件,可以在MediaMuxer线程中作处理,倒计时30秒后,关闭合成器,然后重新打开,具体见下面代码注释:

对于不丢帧的原理是:

编码后的h264数据和AAC数据都会被存到MediaMuxer线程的队列中,我们在MediaMuxer线程中循环读取队列数据做合成。在关闭mediamuxer保存一个mp4再到重新打开MediaMuxer的过程中,数据是被存到MediaMuxers的队列中,并没有丢失。

@Override
public void run() {super.run();// 初始化混合器initMuxer();while (!isExit) {if (isMuxerStart()) {if (muxerDatas.isEmpty()) {synchronized (lock) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}} else {MuxerData data = muxerDatas.remove(0);int track;if (data.trackIndex == TRACK_VIDEO) {track = videoTrackIndex;} else {track = audioTrackIndex;}Log.e(TAG, "写入混合数据 " + data.bufferInfo.size);try {mediaMuxer.writeSampleData(track, data.byteBuf, data.bufferInfo);} catch (Exception e) {Log.e(TAG, "写入混合数据失败!" + e.toString());}//检测是否有过了30秒,大于等于30秒关闭合成器保存mp4文件。//然后再重新新建一个mp4合成器。//在关闭和重新打开的过程中,数据是被保存在muxerDatas这个队列中,//不用担心数据的丢失。long currentTime = System.currentTimeMillis();if( (currentTime - mStartTime) >=30*1000 ){readyStop();try {fileSwapHelper.requestSwapFile(true);String filePath = fileSwapHelper.getNextFileName();mediaMuxer = new MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);mediaMuxer.setOrientationHint(270);mediaMuxer.addTrack(mVideoMediaFormat);mediaMuxer.addTrack(mAudioMediaFormat);mediaMuxer.start();mStartTime = currentTime;} catch (IOException e) {e.printStackTrace();}}}} else {synchronized (lock) {try {Log.e(TAG, "等待音视轨添加...");lock.wait();} catch (InterruptedException e) {e.printStackTrace();Log.e(TAG, "addTrack 异常:" + e.toString());}}}}readyStop();Log.e(TAG, "混合器退出...");
}

三、软编码,camera openSL ffmpeg libx264 libfdk-aac 实现视频录制。

录制流程图如下:

录制详细请参考原文:https://blog.csdn.net/mabeijianxi/article/details/72983362

https://blog.csdn.net/mabeijianxi/article/details/63335722

  1. 编译ffmpeg同时集成libx264和libfdk-aac。
  2. camera预览数据YUV送入H264编码的队列queue,H264编码线程循环从queue中取出数据编码保存h264。

@Override

public void onPreviewFrame(byte[] data, Camera camera) {

camera.addCallbackBuffer(data);

if(mAvRecord != null){

//通过摄像头预览获取yuv数据,通过jni传到native层。

mAvRecord.addVideoFrame(data);

}

}

private native void addVideoFrame(byte[] input);

//编码器配置

pCodecCtx = video_st->codec;

pCodecCtx->codec_id = AV_CODEC_ID_H264;

pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

pCodecCtx->width = arguments->out_width;

pCodecCtx->height = arguments->out_height;

pCodecCtx->bit_rate = arguments->video_bit_rate;

pCodecCtx->gop_size = 50;

pCodecCtx->thread_count = 12;

//帧率分子分母形式。

pCodecCtx->time_base.num = 1;

pCodecCtx->time_base.den = arguments->frame_rate;

//启动编码线程

void *JXYUVEncodeH264::startEncode(void *obj) {

JXYUVEncodeH264 *h264_encoder = (JXYUVEncodeH264 *) obj;

while (!h264_encoder->is_end||!h264_encoder->frame_queue.empty()) {

if(h264_encoder->is_release){

//Write file trailer

av_write_trailer(h264_encoder->pFormatCtx);

if (h264_encoder->video_st) {

avcodec_close(h264_encoder->video_st->codec);

av_free(h264_encoder->pFrame);

}

avio_close(h264_encoder->pFormatCtx->pb);

avformat_free_context(h264_encoder->pFormatCtx);

delete h264_encoder;

return 0;

}

if (h264_encoder->frame_queue.empty()) {

continue;

}

uint8_t *picture_buf = *h264_encoder->frame_queue.wait_and_pop().get();

LOGI(JNI_DEBUG, "send_videoframe_count:%d", h264_encoder->frame_count);

int in_y_size = h264_encoder->arguments->in_width * h264_encoder->arguments->in_height;

h264_encoder->custom_filter(h264_encoder, picture_buf, in_y_size,

h264_encoder->arguments->v_custom_format);

//PTS

h264_encoder->pFrame->pts = h264_encoder->frame_count;

h264_encoder->frame_count++;

int got_picture = 0;

//Encode

int ret = avcodec_encode_video2(h264_encoder->pCodecCtx, &h264_encoder->pkt,

h264_encoder->pFrame, &got_picture);

if (ret < 0) {

LOGE(JNI_DEBUG, "Failed to encode! \n");

}

if (got_picture == 1) {

LOGI(JNI_DEBUG, "Succeed to encode frame: %5d\tsize:%5d\n", h264_encoder->framecnt,

h264_encoder->pkt.size);

h264_encoder->framecnt++;

h264_encoder->pkt.stream_index = h264_encoder->video_st->index;

ret = av_write_frame(h264_encoder->pFormatCtx, &h264_encoder->pkt);

av_free_packet(&h264_encoder->pkt);

}

delete (picture_buf);

}

if (h264_encoder->is_end) {

h264_encoder->encodeEnd();

delete h264_encoder;

}

return 0;

}

  1. 音频通过opensl录音送入AAC编码队列queue,音频编码线程循环从queue中取出数据编码保存h264。

//编码器配置

pCodecCtx = audio_st->codec;

pCodecCtx->codec_id = AV_CODEC_ID_AAC;

pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;

pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;

pCodecCtx->sample_rate = arguments->audio_sample_rate;

pCodecCtx->channel_layout = AV_CH_LAYOUT_MONO;

pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);

pCodecCtx->bit_rate = arguments->audio_bit_rate;

2、定时30秒合成一个mp4,可以用下面逻辑来实现:

原文在视频编码开启一个线程,音频编码开启了一个线程,然后在各自线程中分别保存h264文件和AAC文件。

为了实现30秒保存一个文件,并且保证不丢帧:

我们可以另外启动一个线程,这个线程维护一个avPacket队列。我们把音视频编码出来的avPacket都push到这个队列中,然后开启循环读取队列,在这个线程中做h264和aac文件的保存。这样就可以定时30秒同时保存文件了。然后做合成。

原文是用ffmpeg 命令做合成的,命令只使用的独立进程。如果做在线程中调用,需要用代码来实现,代码实现如下:

int common_muxer(const char *in_filename_v,const char *in_filename_a,const char *out_filename) {AVOutputFormat *ofmt = NULL;AVFormatContext *ifmt_ctx_v = NULL;AVFormatContext *ifmt_ctx_a = NULL;AVFormatContext *ofmt_ctx = NULL;AVPacket pkt;int ret;int i;int videoindex_v=-1;int videoindex_out=-1;int audioindex_a=-1;int audioindex_out=-1;int frame_index=0;int64_t cur_pts_v=0;int64_t cur_pts_a=0;//    const char *in_filename_v = "/storage/emulated/0/DiDiRecord/test2222.mp4";
//    const char *in_filename_a = "/storage/emulated/0/DiDiRecord/test2222.aac";
//    const char *out_filename = "/storage/emulated/0/DiDiRecord/my_test_ff.mp4";//Output file URLav_register_all();//Inputif ((ret = avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0)) < 0) {LOGE( "Could not open input file.");goto end;}if ((ret = avformat_find_stream_info(ifmt_ctx_v, 0)) < 0) {LOGE( "Failed to retrieve input stream information");goto end;}if ((ret = avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0)) < 0) {LOGE( "Could not open input file.");goto end;}if ((ret = avformat_find_stream_info(ifmt_ctx_a, 0)) < 0) {LOGE( "Failed to retrieve input stream information");goto end;}LOGE("===========Input Information==========\n");av_dump_format(ifmt_ctx_v, 0, in_filename_v, 0);av_dump_format(ifmt_ctx_a, 0, in_filename_a, 0);LOGE("======================================\n");//Outputavformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);if (!ofmt_ctx) {LOGE( "Could not create output context\n");ret = AVERROR_UNKNOWN;goto end;}ofmt = ofmt_ctx->oformat;for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {//Create output AVStream according to input AVStreamif(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){AVStream *in_stream = ifmt_ctx_v->streams[i];AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);videoindex_v=i;if (!out_stream) {LOGE( "Failed allocating output stream\n");ret = AVERROR_UNKNOWN;goto end;}videoindex_out=out_stream->index;//Copy the settings of AVCodecContextif (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {LOGE( "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codec->codec_tag = 0;if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;break;}}for (i = 0; i < ifmt_ctx_a->nb_streams; i++) {//Create output AVStream according to input AVStreamif(ifmt_ctx_a->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){AVStream *in_stream = ifmt_ctx_a->streams[i];AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);audioindex_a=i;if (!out_stream) {LOGE( "Failed allocating output stream\n");ret = AVERROR_UNKNOWN;goto end;}audioindex_out=out_stream->index;//Copy the settings of AVCodecContextif (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {LOGE( "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codec->codec_tag = 0;if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;break;}}printf("==========Output Information==========\n");av_dump_format(ofmt_ctx, 0, out_filename, 1);printf("======================================\n");
//Open output fileif (!(ofmt->flags & AVFMT_NOFILE)) {if (avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {LOGE( "Could not open output file '%s'", out_filename);goto end;}}//Write file headerif (avformat_write_header(ofmt_ctx, NULL) < 0) {LOGE( "Error occurred when opening output file\n");goto end;}while (1) {AVFormatContext *ifmt_ctx;int stream_index=0;AVStream *in_stream;AVStream *out_stream;//Get an AVPacketif(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0){ifmt_ctx=ifmt_ctx_v;stream_index=videoindex_out;if(av_read_frame(ifmt_ctx, &pkt) >= 0){do{in_stream  = ifmt_ctx->streams[pkt.stream_index];out_stream = ofmt_ctx->streams[stream_index];if(pkt.stream_index==videoindex_v){
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTSif(pkt.pts==AV_NOPTS_VALUE){LOGE("video av no pts value")
//Write PTSAVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameterspkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);pkt.dts=pkt.pts;pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);frame_index++;}cur_pts_v=pkt.pts;break;}}while(av_read_frame(ifmt_ctx, &pkt) >= 0);}else{break;}}else{ifmt_ctx=ifmt_ctx_a;stream_index=audioindex_out;if(av_read_frame(ifmt_ctx, &pkt) >= 0){do{in_stream  = ifmt_ctx->streams[pkt.stream_index];out_stream = ofmt_ctx->streams[stream_index];if(pkt.stream_index==audioindex_a){//FIX:No PTS
//Simple Write PTSif(pkt.pts==AV_NOPTS_VALUE){LOGE("audio av no pts value")
//Write PTSAVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameterspkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);pkt.dts=pkt.pts;pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);frame_index++;}cur_pts_a=pkt.pts;break;}}while(av_read_frame(ifmt_ctx, &pkt) >= 0);}else{break;}}//Convert PTS/DTSpkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);pkt.pos = -1;pkt.stream_index=stream_index;LOGE("Write 1 Packet. size:%5d\tpts:%lld\n",pkt.size,pkt.pts);
//Writeif (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {LOGE( "Error muxing packet\n");break;}av_free_packet(&pkt);}
//Write file trailerav_write_trailer(ofmt_ctx);end:avformat_close_input(&ifmt_ctx_v);avformat_close_input(&ifmt_ctx_a);
/* close output */if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)){avio_close(ofmt_ctx->pb);}avformat_free_context(ifmt_ctx_a);avformat_free_context(ifmt_ctx_v);avformat_free_context(ofmt_ctx);if (ret < 0 && ret != AVERROR_EOF) {LOGE( "Error occurred.\n");return ret;}return ret;}

Android视频采集方案相关推荐

  1. .VC++关于的VFW视频采集方案【转 360doc】

    2.2  VFW视频采集方案 VFW是Microsoft于1992年推出的数字视频软件包,它不依赖于专用的硬件设备,提供了通用的数字视频开发方案.VFW主要由AVICap.dll.MSVideo.dl ...

  2. VFW视频采集方案(Captureparms参数详细)

    2.2 VFW视频采集方案 VFW是Microsoft于1992年推出的数字视频软件包,它不依赖于专用的硬件设备,提供了通用的数字视频开发方 案.VFW主要由AVICap.dll.MSVideo.dl ...

  3. Android视频采集实时推送RTP/RTSP/RTMP

    因为在工作中,接触到了视频相关的开发工作:同时,大多数android处理音视频多半都是有C++工程师提供处理库,所以,在这里记录一下我自己在工作中遇到的问题. 主要功能: 采集Android摄像头数据 ...

  4. android视频采集

    视频画面的采集主要是使用各个平台提供的摄像头API来实现的, 在为摄像头设置了合适的参数之后,将摄像头实时采集的视频帧渲染到 屏幕上提供给用户预览,然后将该视频帧编码到一个视频文件中,其使 用的编码格 ...

  5. Android视频采集与处理

    Android中很多基本的架构都是C/S层架构,客户端提供调用接口,而实现工作则是在服务端完成.Android Camera的架构也是C/S架构,Client进程虽然不曾拥有任何实质的Camera数据 ...

  6. android视频采集与压缩,视频压缩 Android原生插件

    更新记录 1.0.3(2021-02-23) 1.修复某些类型视频压缩失败问题 1.0.2(2020-10-26) 1.支持视频压缩后,获取第一帧原尺寸图片 2.单独开放出获取视频第一帧原尺寸图片方法 ...

  7. android log 码率,webrtc之Android视频质量提升:保帧率降码率

    前言: 我们的产品是在一款跑着Android系统的特定芯片上使用webrtc开发的一个视频通话业务,当前的情况是在网络正常的情况下帧率也比较低,弱网环境下适应能力较差.基于此,我了解了webrtc A ...

  8. 移动互联网实时视频通讯之视频采集

    原文:http://blog.easemob.com/?p=277 一 .前言 一套完整的实时网络视频通讯系统包括视频采集.视频编码.视频传输.视频解码和播放.对于视频采集,大多数视频编码器对输入原始 ...

  9. 用FFmpeg玩转Android视频录制与压缩

    [置顶] 利用FFmpeg玩转Android视频录制与压缩(二) 标签: Android视频采集Android视频编码Android FFmpegAndroid 视频压缩视频编码 2017-06-10 ...

最新文章

  1. Tips——IndexSearcher自动更新
  2. 2018-2019-2 20165315 《网络对抗技术》Exp2+ 后门进阶
  3. HTML !DOCTYPE 标签
  4. 利用Matlab优化工具箱解数独问题
  5. pomelo中的next
  6. python 忽略 异常_如何忽略Python中的异常?
  7. Effective objective-C 读书笔记 (第一部分)
  8. python文件按行读取变为嵌套列表_迭代两个嵌套的2D列表,其中list2具有list1的行号...
  9. opencv基础--图像模板匹配
  10. 用台式机搭建服务器测试环境_2020年十大最佳台式机环境
  11. DAY09 NETWORK Cisco简单不同网络主机通信
  12. java中常见对象——StringBuffer
  13. Python爬虫教程,利用Python采集QQ群成员信息
  14. 网站服务器历史解析记录查询,域名解析ip历史查询
  15. 百度搜索引擎都有哪些算法
  16. Linux编辑器-vim的使用的 “打字练习“
  17. 【Verilog】基于FPGA的打地鼠小游戏设计(VGA显示、附代码、演示视频)
  18. 关于 Elasticsearch 429 Too Many Requests 的 排查思考
  19. 如何在linux上的上修改配置ip地址
  20. 简单便宜智能家居解决方案

热门文章

  1. 远创智控Modbubs转Profinet网关连接海利普变频器的使用方法
  2. 计算机在表格顶端添加标题,给永中Office多页表格添加相同的标题 -电脑资料
  3. 4G工业路由器地铁站环境监测
  4. Python- 查找最小公倍数
  5. 研究生学python方便写论文嘛_研究生写论文,千万不要给自己挖坑!
  6. gava java_guava | 并发编程网 – ifeve.com
  7. 微信自带的翻译功能,让你与外国朋友轻松沟通
  8. 贝加莱PLC以太网采集方案
  9. python 文件操作 和 标准库
  10. Java 根据Excel模板 导出Excel报表