由于老版本的ffmpeg一些使用方法将要废弃如streams[videoStream]->codec这种方式查找解码器就不能用了,再使用就会报警告,或者报错,这里使用新版ffmpeg接口制作播放器。
先看播放器功能(这里有android版的):

带有音视频同步,快慢速播放,本地及网络视频播放等,经测试支持所有网络视频流,目前还没有发现播放不出的网络视频流。这是一个完整的播放器,后面有源码下载地址。本篇文章先说一下ffmpeg的使用,这个视频播放器的解码播放流程如下图:

这里android版的有多线程运行图,共用到4个线程,跟ios版的基本一样,就是ios版是主线程opengl绘图,android是默认opengl线程绘图。
这个源码对应上面图中流程除了opengl播放rgb没有实现之外其他都有源码实现。现在生效的流程是用opengl直接播放yuv格式。
先看性能测试,播放720及1080格式的视频cpu使用率,测试手机,用了三年的iPhone6,算是很老了,720p:

cpu消耗只有百分之十几,一般直播高清的也就是这个分辨率了,性能表现还是很好的。
下面看一下全高清的1080p:

cpu消耗在50%左右。大家可以自行测试用opengl播放rgb格式cpu消耗很定比这个高得多的多。播放image估计1080的就卡的播不了。
因为模拟器没有gpu,要用cpu模拟gpu,所以这个代码在模拟器上跑cpu一直满载
这里不说ffmpeg细节问题网上很多自行去了解。这里贴部分播放器代码,主要是解码部分,并稍作说明:
FFmpegDecoder.h

//
//  FFmpegDecoder.h
//  FFmpeg-project
//
//  Created by huizai on 2017/9/14.
//  Copyright © 2017年 huizai. All rights reserved.
//#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "avcodec.h"
#import "swscale.h"
#import "avformat.h"
#import "swresample.h"
#import "samplefmt.h"
#import "YUV_GL_DATA.h"@interface FFmpegDecoder : NSObject
//总时长
@property (nonatomic,assign)int  totalMs;
//音频播放时间
@property (nonatomic,assign)int  aFps;
//视频播放时间
@property (nonatomic,assign)int  vFps;
//视频流索引
@property (nonatomic,assign)int  videoStreamIndex;
//音频流索引
@property (nonatomic,assign)int  audioStreamIndex;
@property (nonatomic,assign)int  sampleRate;
@property (nonatomic,assign)int  sampleSize;
@property (nonatomic,assign)int  channel;
//音频贞数据的长度
@property (nonatomic,assign)int  pcmDataLength;#pragma mark - 接口
- (BOOL)OpenUrl:(const char*)path;
- (void)Read:(AVPacket*)pkt;
- (void)Decode:(AVPacket*)pkt;
- (H264YUV_Frame)YuvToGlData:(H264YUV_Frame)yuvFrame;
- (BOOL)ToRGB:(char*)outBuf andWithOutHeight:(int)outHeight andWithOutWidth:(int)outWidth;
- (UIImage*)ToImage:(char*)dataBuf andWithOutHeight:(int)outHeight andWithOutWidth:(int)outWidth;
//音频重采样
- (int)ToPCM:(char*)dataBuf;
//获取错误信息
- (NSString*)GetError;
- (void)Close;@end

FFmpegDecoder.m

//
//  FFmpegDecoder.m
//  FFmpeg-project
//
//  Created by huizai on 2017/9/14.
//  Copyright © 2017年 huizai. All rights reserved.
//#import "FFmpegDecoder.h"//定义音频重采样后的参数
#define SAMPLE_SIZE 16
#define SAMPLE_RATE 44100
#define CHANNEL     2@implementation FFmpegDecoder{char errorBuf[1024];NSLock   *lock;AVFormatContext   * pFormatCtx;AVCodecContext    * pVideoCodecCtx;AVCodecContext    * pAudioCodecCtx;AVFrame           * pYuv;        AVFrame           * pPcm;AVCodec           * pVideoCodec; //视频解码器AVCodec           * pAudioCodec; //音频解码器struct SwsContext * pSwsCtx;SwrContext        * pSwrCtx;char              * rgb;UIImage           * tempImage;
}- (id)init{if (self = [super init]) {[self initParam];return self;}return nil;
}- (void)initParam{av_register_all();avformat_network_init();_pcmDataLength = 0;_sampleRate = SAMPLE_RATE;_sampleSize = SAMPLE_SIZE;_channel = CHANNEL;lock = [[NSLock alloc]init];
}- (double)r2d:(AVRational)r{return r.num == 0 || r.den == 0 ? 0.:(double)r.num/(double)r.den;
}
//初始化ffmpeg
- (BOOL)OpenUrl:(const char*)path{[self Close];[lock lock];int re = avformat_open_input(&pFormatCtx, path, 0, 0);if (re != 0) {[lock unlock];av_strerror(re, errorBuf, sizeof(errorBuf));return false;}_totalMs = (int)(pFormatCtx->duration/AV_TIME_BASE)*1000;avformat_find_stream_info(pFormatCtx, NULL);//分别找到音频视频解码器并打开解码器for (int i = 0; i < pFormatCtx->nb_streams; i++) {AVStream *stream = pFormatCtx->streams[i];AVCodec * codec = avcodec_find_decoder(stream->codecpar->codec_id);AVCodecContext * codecCtx = avcodec_alloc_context3(codec);avcodec_parameters_to_context(codecCtx, stream->codecpar);if (codecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {printf("video\n");_videoStreamIndex = i;pVideoCodec  = codec;pVideoCodecCtx = codecCtx;int err = avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL);if (err != 0) {[lock unlock];char buf[1024] = {0};av_strerror(err, buf, sizeof(buf));printf("open videoCodec error:%s", buf);return false;}}if (codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {printf("audio\n");_audioStreamIndex = i;pAudioCodec  = codec;pAudioCodecCtx = codecCtx;int err = avcodec_open2(pAudioCodecCtx, pAudioCodec, NULL);if (err != 0) {[lock unlock];char buf[1024] = {0};av_strerror(err, buf, sizeof(buf));printf("open audionCodec error:%s", buf);return false;}if (codecCtx->sample_rate != SAMPLE_RATE) {_sampleRate = codecCtx->sample_rate;}}}printf("open acodec success! sampleRate:%d  channel:%d  sampleSize:%d fmt:%d\n",_sampleRate,_channel,_sampleSize,pAudioCodecCtx->sample_fmt);[lock unlock];return true;
}
//读取音视频packet
- (void)Read:(AVPacket*)pkt{//这里先不加线程锁,在启动多线程的地方统一加锁// AVPacket * pkt = malloc(sizeof(AVPacket));if (!pFormatCtx) {av_packet_unref(pkt);return;}int err = av_read_frame(pFormatCtx, pkt);if (err != 0) {av_strerror(err, errorBuf, sizeof(errorBuf));printf("av_read_frame error:%s",errorBuf);av_packet_unref(pkt);return ;}
}
//解码过程这里音频视频解码放一块了,中间有一些判断是音频还是视频分别处理,
- (void)Decode:(AVPacket*)pkt{if (!pFormatCtx) {return ;}//分配AVFream 空间if (pYuv == NULL) {pYuv = av_frame_alloc();}if (pPcm == NULL) {pPcm = av_frame_alloc();}AVCodecContext * pCodecCtx;AVFrame * tempFrame;if (pkt->stream_index == _videoStreamIndex) {pCodecCtx = pVideoCodecCtx;tempFrame = pYuv;}else if (pkt->stream_index == _audioStreamIndex){pCodecCtx = pAudioCodecCtx;tempFrame = pPcm;}else{return;}if (!pCodecCtx) {return;}int re = avcodec_send_packet(pCodecCtx, pkt);if (re != 0) {return;}re = avcodec_receive_frame(pCodecCtx, tempFrame);//解码后再获取pts  解码过程有缓存if (pkt->stream_index == _videoStreamIndex) {_vFps = (pYuv->pts *[self r2d:(pFormatCtx->streams[_videoStreamIndex]->time_base)])*1000;}else if (pkt->stream_index == _audioStreamIndex){_aFps = (pPcm->pts * [self r2d:(pFormatCtx->streams[_audioStreamIndex]->time_base)])*1000;}printf("[D]");return;
}
//这里是给视频对齐的,并封装成opengl需要的结构体格式数据,YUV格式
- (H264YUV_Frame)YuvToGlData:(H264YUV_Frame)yuvFrame{if (!pFormatCtx || !pYuv || pYuv->linesize[0] <= 0) {return yuvFrame;}//把数据重新封装成opengl需要的格式unsigned int lumaLength= (pYuv->height)*(MIN(pYuv->linesize[0], pYuv->width));unsigned int chromBLength=((pYuv->height)/2)*(MIN(pYuv->linesize[1], (pYuv->width)/2));unsigned int chromRLength=((pYuv->height)/2)*(MIN(pYuv->linesize[2], (pYuv->width)/2));yuvFrame.luma.dataBuffer = malloc(lumaLength);yuvFrame.chromaB.dataBuffer = malloc(chromBLength);yuvFrame.chromaR.dataBuffer = malloc(chromRLength);yuvFrame.width=pYuv->width;yuvFrame.height=pYuv->height;if (pYuv->height <= 0) {free(yuvFrame.luma.dataBuffer);free(yuvFrame.chromaB.dataBuffer);free(yuvFrame.chromaR.dataBuffer);return yuvFrame;}copyDecodedFrame(pYuv->data[0],yuvFrame.luma.dataBuffer,pYuv->linesize[0],pYuv->width,pYuv->height);copyDecodedFrame(pYuv->data[1], yuvFrame.chromaB.dataBuffer,pYuv->linesize[1],pYuv->width / 2,pYuv->height / 2);copyDecodedFrame(pYuv->data[2], yuvFrame.chromaR.dataBuffer,pYuv->linesize[2],pYuv->width / 2,pYuv->height / 2);return yuvFrame;}void copyDecodedFrame(unsigned char *src, unsigned char *dist,int linesize, int width, int height)
{width = MIN(linesize, width);if (sizeof(dist) == 0) {return;}for (NSUInteger i = 0; i < height; ++i) {memcpy(dist, src, width);dist += width;src += linesize;}
}
//音频重采样,就是要把很多种未知的的音频格式转换成一定的采样率,声道数等等并以PCM格式输出,就可以给音频播放器播放了
- (int)ToPCM:(char*)dataBuf{if (!pFormatCtx || !pPcm || !dataBuf) {return 0;}printf("sample_rate:%d,channels:%d,sample_fmt:%d,channel_layout:%llu,nb_samples:%d\n",pAudioCodecCtx->sample_rate,pAudioCodecCtx->channels,pAudioCodecCtx->sample_fmt,pAudioCodecCtx->channel_layout,pPcm->nb_samples);//音频重采样if (pSwrCtx == NULL) {pSwrCtx = swr_alloc();swr_alloc_set_opts(pSwrCtx,AV_CH_LAYOUT_STEREO,//2声道立体声AV_SAMPLE_FMT_S16,  //采样大小 16位_sampleRate,        //采样率pAudioCodecCtx->channel_layout,pAudioCodecCtx->sample_fmt,// 样本类型pAudioCodecCtx->sample_rate,0, 0);swr_init(pSwrCtx);}uint8_t * data[1];[lock lock];data[0] = (uint8_t*)dataBuf;int len = swr_convert(pSwrCtx, data, 10000, (const uint8_t**)pPcm->data, pPcm->nb_samples);if (len < 0) {[lock unlock];return 0;}int outSize = av_samples_get_buffer_size(NULL,CHANNEL,len,AV_SAMPLE_FMT_S16,0);_pcmDataLength = outSize;NSLog(@"nb_smples:%d,des_smples:%d,outSize:%d",pPcm->nb_samples,len,outSize);[lock unlock];return outSize;
}
//这个跟音频重采样对应的,就是视频从yuv转换成rgb或者bgr转换后可以opengl固定管线绘图,固定管线不用写opengl的shader简单一些,或者进一步转换成image,当然直接opengl播放yuv就没必要用这个方法了
- (BOOL)ToRGB:(char*)outBuf andWithOutHeight:(int)outHeight andWithOutWidth:(int)outWidth{if (!pFormatCtx || !pYuv || pYuv->linesize[0] <= 0) {return false;}//视频yuv转rgb 并转换视频frame的大小pSwsCtx = sws_getCachedContext(pSwsCtx, pVideoCodecCtx->width,pVideoCodecCtx->height,pVideoCodecCtx->pix_fmt,outWidth, outHeight,AV_PIX_FMT_RGB24,SWS_BICUBIC, NULL, NULL, NULL);if (pSwsCtx) {// printf("sws_getCachedContext success!\n");}else{printf("sws_getCachedContext fail!\n");return nil;}uint8_t * data[AV_NUM_DATA_POINTERS]={0};data[0] = (uint8_t*)outBuf;int linesize[AV_NUM_DATA_POINTERS] = {0};linesize[0] = outWidth * 4;int h = sws_scale(pSwsCtx, (const uint8_t* const*)pYuv->data, pYuv->linesize, 0, pVideoCodecCtx->height,data,linesize);if (h > 0) {printf("H:%d",h);}return true;
}
//rgb数据片转换成image,属于ios范畴,这里的色彩格式需要跟ToRGB里面输出的格式一致,如rgb或者rgba等一致即可
- (UIImage*)ToImage:(char*)dataBuf andWithOutHeight:(int)outHeight andWithOutWidth:(int)outWidth{//rgb 数据转换成为image  int linesize[AV_NUM_DATA_POINTERS] = {0};linesize[0] = outWidth * 4;CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();void * colorData = NULL;memcpy(&colorData, &dataBuf, sizeof(dataBuf));CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, colorData, sizeof(colorData), NULL);CGImageRef cgImage = CGImageCreate(outWidth,outHeight,8,24,linesize[0],colorSpace,bitmapInfo,provider,NULL,NO,kCGRenderingIntentDefault);UIImage * image = [UIImage imageWithCGImage:cgImage];CGImageRelease(cgImage);CGColorSpaceRelease(colorSpace);CGDataProviderRelease(provider);tempImage = image;return image;
}- (void)Close{[lock lock];if (pFormatCtx) {avformat_close_input(&pFormatCtx);}if (pSwrCtx) {swr_free(&pSwrCtx);}if (pSwsCtx) {sws_freeContext(pSwsCtx);}avcodec_close(pVideoCodecCtx);avcodec_close(pAudioCodecCtx);if (pYuv) {av_frame_free(&pYuv);}if (pPcm) {av_frame_free(&pPcm);}[lock unlock];
}- (NSString*)GetError{[lock lock];NSString * err = [NSString stringWithUTF8String:errorBuf];[lock unlock];return err;
}@end

多线程播放部分:

//启动gcd多线程,读取解码播放
- (void)startPlayThread{dispatch_queue_t readQueue = dispatch_queue_create("readAudioQueeu", DISPATCH_QUEUE_CONCURRENT);dispatch_async(readQueue, ^{AVPacket *pkt = NULL;while (!isExit) {pkt = av_packet_alloc();[lock lock];[decoder Read:pkt];[lock unlock];if (pkt == NULL) {[NSThread sleepForTimeInterval:0.01];continue;}if (pkt->size <= 0) {[NSThread sleepForTimeInterval:0.01];continue;}if (pkt->stream_index == decoder.audioStreamIndex) {[lock lock];[decoder Decode:pkt];[lock unlock];av_packet_unref(pkt);char* tempData = (char*)malloc(10000);[lock lock];int length =  [decoder ToPCM:tempData];[lock unlock];//用音频播放器播放[audioPlayer openAudioFromQueue:tempData andWithDataSize:length andWithSampleRate:decoder.sampleRate andWithAbit:decoder.sampleSize andWithAchannel:decoder.channel];free(tempData);//这里设置openal内部缓存数据的大小  太大了视频延迟大  太小了视频会卡顿 根据实际情况调整NSLog(@"++++++++++++++%d",audioPlayer.m_numqueued);if (audioPlayer.m_numqueued > 10 && audioPlayer.m_numqueued < 35) {[NSThread sleepForTimeInterval:0.01];}else if (audioPlayer.m_numqueued > 35){[NSThread sleepForTimeInterval:0.025];}continue;}else if (pkt->stream_index == decoder.videoStreamIndex){[lock lock];NSData * pktData = [NSData dataWithBytes:pkt length:sizeof(AVPacket)];[vPktArr insertObject:pktData atIndex:0];[lock unlock];continue;//  av_packet_unref(pkt);}else{av_packet_unref(pkt);continue;}}});dispatch_queue_t videoPlayQueue = dispatch_queue_create("videoPlayQueeu", DISPATCH_QUEUE_CONCURRENT);dispatch_async(videoPlayQueue, ^{H264YUV_Frame  yuvFrame;while (!isExit) {if (vPktArr.count == 0) {[NSThread sleepForTimeInterval:0.01];NSLog(@"0000000000000000000000");continue;}//这里同步音视频播放速度if ((decoder.vFps > decoder.aFps - 900 - _syncRate*1000)&& decoder.aFps>500) {NSLog(@"aaaaaaaaaaaaaaaaaaa");[NSThread sleepForTimeInterval:0.01];continue;}[lock lock];NSData * newData = [vPktArr lastObject];AVPacket* newPkt = (AVPacket*)[newData bytes];[vPktArr removeLastObject];[lock unlock];if (!newPkt) {continue;}[lock lock];[decoder Decode:newPkt];[lock unlock];av_packet_unref(newPkt);/*下面这段屏蔽代码是yuv转rgbrgb转image的如果不用opengl直接绘图yuv可以用下面的功能*/
//            int width = 320;
//            int height = 250;
//            char* tempData = (char*)malloc(width*height*4 + 1);
//            [lock lock];
//            [decoder ToRGB:tempData andWithOutHeight:height andWithOutWidth:width];
//            image = [decoder ToImage:tempData andWithOutHeight:height andWithOutWidth:width];
//            free(tempData);
//            [lock unlock];
//            UIColor *color =  [UIColor colorWithPatternImage:image];[lock lock];memset(&yuvFrame, 0, sizeof(H264YUV_Frame));yuvFrame = [decoder YuvToGlData:yuvFrame];if (yuvFrame.width == 0) {[lock unlock];continue;}[lock unlock];dispatch_async(dispatch_get_main_queue(), ^{// [self.imageView setImage:image];// playView.backgroundColor = color;[gl displayYUV420pData:(H264YUV_Frame*)&yuvFrame];free(yuvFrame.luma.dataBuffer);free(yuvFrame.chromaB.dataBuffer);free(yuvFrame.chromaR.dataBuffer);});}});
}

更多部分如opengl播放yuv部分,openal播放pcm部分请下载源码查看。对于这部分后面我会补文章做说明。
由于ffmpeg库文件太大csdn上传大小有限制,我把ffmpeg库文件放百度云,下载下来直接解压到FFmpeglib目录里面即可:

源码下载是要积分的,花了很多时间来做这个demo除了帮助大家学习我还是打算挣点积分的。
源码下载地址:
http://download.csdn.net/download/m0_37677536/10153709
ffmpeg_lib库下载地址,当然自己编译也可以:
http://download.csdn.net/download/m0_37677536/10153764
或者:https://pan.baidu.com/s/1eRDqlge 密码:c27z
ios版ffmpeg_lib编译方法:
http://blog.csdn.net/m0_37677536/article/details/77934844
ios opengl 播放 yuv:
http://blog.csdn.net/m0_37677536/article/details/78782501
ios openal播放 pcm:
http://blog.csdn.net/m0_37677536/article/details/79013577

iOS ffmpeg+OpenGL播放yuv+openAL 快放 慢放 视频播放器相关推荐

  1. android ffmpeg+OpenGL播放yuv+openSL 快放 慢放 视频播放器

    这里是完整的音视频播放器,功能如下(这里有iOS版的): 视频是通过opengl 播放yuv数据,音频是opensl播放. app运行流程如下图: 红色虚线内的是一个线程的运行,总共涉及到四个线程. ...

  2. OpenGL播放yuv数据流(着色器SHADER)-IOS(一)

    OpenGL播放yuv数据流(着色器SHADER)-IOS(一) 和windows平台的类似,只是用object-c编写的,着色器语言shader,rgb转yuv有些不同,具体看代码注释. //.h ...

  3. OpenGL播放yuv数据流(着色器SHADER)-windows(一)

    OpenGL播放yuv数据流(着色器SHADER)-windows(一) 在写这篇文章之前首先要感谢老雷,http://blog.csdn.net/leixiaohua1020/article/det ...

  4. ios 边录音边放_iOS 音频视频播放器实现边下载边播放缓存视频

    框架整体介绍 功能介绍 KJPlayer 是一款视频播放器,AVPlayer的封装,继承UIView 1.支持播放网络和本地视频 ☑️ 2.播放多种格式mp4 ☑️ m3u8.3gp.mov等等暂未完 ...

  5. 播放视频android学习笔记---44_在线视频播放器,网络视频解析器,SurfaceView 控件使用方法...

    最近用使开辟的过程中涌现了一个小题问,顺便录记一下因原和法方--播放视频 44_在线视频播放器 ------------------------- 1.注意这里,在模拟器中,android2.2和an ...

  6. java jmf播放视频_使用JMF实现java视频播放器

    JMF这个多媒体开发框架太牛了,简单的几句代码就能实现一个视频播放器的开发,厉害,就是支持的格式少了一些,没关系,这个视频播放器可以播放mpg,avi,fvl等等,想播放其他的请开发自己的插件,下面将 ...

  7. python在tk界面播放本地视频_tkinter做一个本地视频播放器(2)——弹幕

    前文我们已经完成了一个集暂停.倍速.显示进度条功能为一体的视频播放器,今天我们再来增加一个新的功能--发送弹幕. tkinter播放视频的原理,就是读取每一帧的图片,然后刷新画布.所以如果想实现弹幕功 ...

  8. 【Vue】播放flv格式视频(flv.js视频播放器)

    图片来源:HTTP-FLV直播初探 链接 github地址:GitHub - bilibili/flv.js: HTML5 FLV Player 播放flv格式视频 npm install flv.j ...

  9. ffmpeg拉流设置暂停_ffmpeg+SDL2实现的视频播放器「退出、暂停、播放」

    前言 这篇记录退出.暂停.播放. 快进快退想了下,没啥思路.囧. 关于退出 一直没怎么管退出,但是始终是个问题,无论是中途退出还是播放完毕退出. 想要做到的: 中途退出(点窗口的x)能立即退出 播放完 ...

最新文章

  1. 「SAP技术」SAP 如何看序列号被包在哪些HU里?
  2. [译]Java 设计模式之命令
  3. linux 安装系统划分lvm分区
  4. JAVA单向/双向链表的实现
  5. C语言中在常数后面加U、L、F的功能
  6. Android初级教程Activity小案例(计算器乘法运算)
  7. 在IIS中寄存已有WCF服务
  8. Istio 中的多集群部署与管理
  9. ArcGIS实验教程——实验十一:影像拼接与提取
  10. Eigen--Array
  11. 拟牛顿法/Quasi-Newton,DFP算法/Davidon-Fletcher-Powell,及BFGS算法/Broyden-Fletcher-Goldfarb-Shanno...
  12. java 修饰符 访问控制符_《Java基础知识》Java访问修饰符(访问控制符)
  13. 【原创】Git版本控制器的基本使用
  14. 书籍:Learn Web Development with Python - 2018.pdf
  15. JAVA 16进制转字符串问题
  16. godot 以 WebAssembly 为目标平台编译导出模板
  17. 数学也荒唐:20个脑洞大开的数学趣题
  18. Vue 移动端实现调用相机扫描二维码或条形码
  19. 深度学习7日入门-CV疫情特辑心得
  20. NB-IoT的DRX、eDRX、PSM三个模式怎么用?通俗解释,看完就懂!

热门文章

  1. glob通配及IO重定向 笔记
  2. Revit插件快速提高建模的效率,分别都有那种功能revit插件
  3. DEV ASPXGRIDVIEW控件中的Findcontrol
  4. 盘点 | 跨平台桌面应用开发的5大主流框架
  5. 虚拟机克隆,将虚拟机以OVF格式导出或导入
  6. 考了10年公务员,如今35岁,终于死心了
  7. 安装配置Discuz论坛
  8. 搜索引擎排名不等于网站的优化
  9. 搜索引擎如何进行排名?
  10. linux 网卡包存储,Linux下使用libpcap进行网络抓包并保存到文件-Go语言中文社区