这个项目是简书2012lc大神写的,播放没问题就是其余功能都有点卡过头了。。。
哎,本身也没能写出一个优秀的播放器,github
回到正题缓存
首先这个代码是生产者和消费者的模式,生成者就是不断地解码mp4将一帧的数据给消费者,消费者就是音频播放类和视频播放类,也就说生成者一个,消费者两个,都是经过pthread开启线程,经过互斥锁和条件信息来维持这个关系链ide
1.生产者—输出一帧帧的数据函数
开始就是初始化各种组件和测试视频文件是否可以打开,并得到视频相关信息为后来代码作准备工做测试
void init() {
LOGE("开启解码线程")
//1.注册组件
av_register_all();
avformat_network_init();
//封装格式上下文
pFormatCtx = avformat_alloc_context();ui
//2.打开输入视频文件 if (avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0) { LOGE("%s", "打开输入视频文件失败"); } //3.获取视频信息 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { LOGE("%s", "获取视频信息失败"); } //获得播放总时间 if (pFormatCtx->duration != AV_NOPTS_VALUE) { duration = pFormatCtx->duration;//微秒 }
}this
初始化音频类和视频类,并将SurfaceView给视频类编码
ffmpegVideo = new FFmpegVideo; ffmpegMusic = new FFmpegMusic; ffmpegVideo->setPlayCall(call_video_play);
开启生成者线程spa
pthread_create(&p_tid, NULL, begin, NULL);//开启begin线程
从视频信息里获取视屏流和音频流,将各自的×××上下文复制分别给与两个消费者类,并将流在哪一个位置、还有时间单位给与两个消费者类
//找到视频流和音频流 for (int i = 0; i < pFormatCtx->nb_streams; ++i) { //获取××× AVCodecContext *avCodecContext = pFormatCtx->streams[i]->codec; AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id); //copy一个×××, AVCodecContext *codecContext = avcodec_alloc_context3(avCodec); avcodec_copy_context(codecContext, avCodecContext); if (avcodec_open2(codecContext, avCodec, NULL) < 0) { LOGE("打开失败") continue; } //若是是视频流 if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { ffmpegVideo->index = i; ffmpegVideo->setAvCodecContext(codecContext); ffmpegVideo->time_base = pFormatCtx->streams[i]->time_base; if (window) { ANativeWindow_setBuffersGeometry(window, ffmpegVideo->codec->width, ffmpegVideo->codec->height, WINDOW_FORMAT_RGBA_8888); } }//若是是音频流 else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { ffmpegMusic->index = i; ffmpegMusic->setAvCodecContext(codecContext); ffmpegMusic->time_base = pFormatCtx->streams[i]->time_base; } }
开启两个消费者类的线程
ffmpegVideo->setFFmepegMusic(ffmpegMusic); ffmpegMusic->play(); ffmpegVideo->play();
而后开始一帧一帧的解码出数据给两个消费者类的用来存储数据的矢量,若是矢量里的数据还有那就没有播放玩,继续播放
while (isPlay) { // ret = av_read_frame(pFormatCtx, packet); if (ret == 0) { if (ffmpegVideo && ffmpegVideo->isPlay && packet->stream_index == ffmpegVideo->index ) { //将视频packet压入队列 ffmpegVideo->put(packet); } else if (ffmpegMusic && ffmpegMusic->isPlay && packet->stream_index == ffmpegMusic->index) { ffmpegMusic->put(packet); } av_packet_unref(packet); } else if (ret == AVERROR_EOF) { // 读完了 //读取完毕 可是不必定播放完毕 while (isPlay) { if (ffmpegVideo->queue.empty() && ffmpegMusic->queue.empty()) { break; } // LOGE("等待播放完成"); av_usleep(10000); } } }
播放完了就中止两个消费者类的线程,并释放资源
isPlay = 0; if (ffmpegMusic && ffmpegMusic->isPlay) { ffmpegMusic->stop(); } if (ffmpegVideo && ffmpegVideo->isPlay) { ffmpegVideo->stop(); } //释放 av_free_packet(packet); avformat_free_context(pFormatCtx); pthread_exit(0);
2.消费者—音频类
开启线程
pthread_create(&playId, NULL, MusicPlay, this);//开启begin线程
就下来就是配置OpenSL ES来播放音频,而这个数据的来源是这一段代码决定的
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
咱们再来看看bqPlayerCallback,数据是从getPcm函数获得的
FFmpegMusic *musicplay = (FFmpegMusic *) context; int datasize = getPcm(musicplay); if(datasize>0){ //第一针所须要时间采样字节/采样率 double time = datasize/(44100*2*2); // musicplay->clock=time+musicplay->clock; LOGE("当前一帧声音时间%f 播放时间%f",time,musicplay->clock); (*bq)->Enqueue(bq,musicplay->out_buffer,datasize); LOGE("播放 %d ",musicplay->queue.size()); }
而后这个getPcm函数里,经过get函数来完成获取一帧数据
agrs->get(avPacket);
若是有矢量里有数据它将矢量里的数据取出,若是没有就等待生产者经过条件变量
//将packet弹出队列
int FFmpegMusic::get(AVPacket avPacket) {
LOGE("取出队列")
pthread_mutex_lock(&mutex);
while (isPlay){
LOGE("取出对垒 xxxxxx")
if(!queue.empty()&&isPause){
LOGE("ispause %d",isPause);
//若是队列中有数据能够拿出来
if(av_packet_ref(avPacket,queue.front())){
break;
}
//取成功了,弹出队列,销毁packet
AVPacket packet2 = queue.front();
queue.erase(queue.begin());
av_free(packet2);
break;
} else{
LOGE("音频执行wait")
LOGE("ispause %d",isPause);
pthread_cond_wait(&cond,&mutex);
} } pthread_mutex_unlock(&mutex); return 0;
}
注意这个获取的数据是AVPacket,咱们须要将他解码为AVFrame才行
if (avPacket->pts != AV_NOPTS_VALUE) { agrs->clock = av_q2d(agrs->time_base) * avPacket->pts; } // 解码 mp3 编码格式frame----pcm frame LOGE("解码") avcodec_decode_audio4(agrs->codec, avFrame, &gotframe, avPacket); if (gotframe) { swr_convert(agrs->swrContext, &agrs->out_buffer, 44100 * 2, (const uint8_t **) avFrame->data, avFrame->nb_samples);
// 缓冲区的大小
size = av_samples_get_buffer_size(NULL, agrs->out_channer_nb, avFrame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
break;
}
回到OpenSL ES的回调函数,取到数据后将数据压入播放器里让他播放
//第一针所须要时间采样字节/采样率 double time = datasize/(44100*2*2); // musicplay->clock=time+musicplay->clock; LOGE("当前一帧声音时间%f 播放时间%f",time,musicplay->clock); (*bq)->Enqueue(bq,musicplay->out_buffer,datasize); LOGE("播放 %d ",musicplay->queue.size());
3.消费者—视频类
这二者的运行过程很像,我这里就省略的说说
开启线程
//申请AVFrame AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体通常用于存储原始数据,指向解码后的原始帧 AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧 AVPacket *packet = (AVPacket *) av_mallocz(sizeof(AVPacket)); //输出文件 //FILE *fp = fopen(outputPath,"wb"); //缓存区 uint8_t *out_buffer= (uint8_t *)av_mallocz(avpicture_get_size(AV_PIX_FMT_RGBA, ffmpegVideo->codec->width,ffmpegVideo->codec->height)); //与缓存区相关联,设置rgb_frame缓存区 avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,ffmpegVideo->codec->width,ffmpegVideo->codec->height); LOGE("转换成rgba格式") ffmpegVideo->swsContext = sws_getContext(ffmpegVideo->codec->width,ffmpegVideo->codec->height,ffmpegVideo->codec->pix_fmt, ffmpegVideo->codec->width,ffmpegVideo->codec->height,AV_PIX_FMT_RGBA, SWS_BICUBIC,NULL,NULL,NULL);
获取一帧数据
ffmpegVideo->get(packet);
而后从矢量里获得数据
调节视频和音频的播放速度
diff = ffmpegVideo->clock - audio_clock;
// 在合理范围外 才会延迟 加快
sync_threshold = (delay > 0.01 ? 0.01 : delay);
if (fabs(diff) < 10) { if (diff <= -sync_threshold) { delay = 0; } else if (diff >=sync_threshold) { delay = 2 * delay; } } start_time += delay; actual_delay=start_time-av_gettime()/1000000.0; if (actual_delay < 0.01) { actual_delay = 0.01; } av_usleep(actual_delay*1000000.0+6000);
播放视频
video_call(rgb_frame);
释放资源并退出线程
LOGE("free packet"); av_free(packet); LOGE("free packet ok"); LOGE("free packet"); av_frame_free(&frame); av_frame_free(&rgb_frame); sws_freeContext(ffmpegVideo->swsContext); size_t size = ffmpegVideo->queue.size(); for (int i = 0; i < size; ++i) { AVPacket *pkt = ffmpegVideo->queue.front(); av_free(pkt); ffmpegVideo->queue.erase(ffmpegVideo->queue.begin()); } LOGE("VIDEO EXIT"); pthread_exit(0);
结束了,之后哎,尽可能本身写出一个播放器,要那种暂停不卡的