1. 注册组件:av_register_all()
2. 打开封装格式->打开文件:av_open_input_file()
3.查找视频流->拿到视频信息:av_find_stream_info()
4.穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
5.查找对应视频的解码器:avcodec_find_decoder()
6. 打开解码器:avcodec_open2()
7. 为解码帧分配内存:avcodec_alloc_frame()
8. 读取视频压缩数据->循环读取 取出每一帧数据:av_read_frame()
9. 判断帧的类型,对于视频帧调用:avcodec_decode_video()
10. 解码完后,释放解码器:avcodec_close()
11. 关闭输入文件:av_close_input_file()
流程图如下:
image.pngXCODE环境配置
添加系统库.png关联ffmpeg头文件.png
引入FFmpeg库头文件
//核心库
#include "libavcodec/avcodec.h"
//封装格式处理库
#include "libavformat/avformat.h"
//工具库
#include "libavutil/imgutils.h"
//视频像素数据格式库
#include "libswscale/swscale.h"
//音频采样数据格式库
#include "libswresample/swresample.h"
视频解码实现
+(void)ffmepgDecodeVideo:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
//第一步:组册组件
const char *infilePath = [jinfilePath UTF8String];
const char *outfilePath = [joutfilePath UTF8String];
av_register_all();
AVFormatContext *avformat_context = avformat_alloc_context();
//第二步:打开封装格式->打开文件
//参数一:封装格式上下文
//作用:保存整个视频信息(解码器、编码器等等...)
//信息:码率、帧率等...
int avformat_open_input_result = avformat_open_input(&avformat_context,
infilePath,
NULL,
NULL);
if (avformat_open_input_result != 0){
NSLog(@"打开文件失败");
return;
}
//第三步:查找视频流->拿到视频信息
//参数一:封装格式上下文
//参数二:指定默认配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
NULL);
if (avformat_find_stream_info_result < 0){
NSLog(@"查找失败");
return;
}
//第四步:查找视频解码器
//1、查找视频流索引位置
int av_stream_index = -1;
for(int i=0;i<avformat_context->nb_streams;i++){
//判断流类型:视频流、音频流、字母流等等...
AVCodecContext *codec = avformat_context->streams[i]->codec;
if(codec->codec_type == AVMEDIA_TYPE_VIDEO){
av_stream_index = i;
}
}
//2、根据视频流索引,获取解码器上下文
AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
//3、根据解码器上下文,获得解码器ID,然后查找解码器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打开解码器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0){
NSLog(@"打开解码器失败");
return;
}
NSLog(@"测试编码器名称:%s",avcodec->name);
//第六步:读取视频压缩数据->循环读取
//1、分析av_read_frame参数
//参数一:封装格式上下文
//参数二:一帧压缩数据 = 一张图片
//av_read_frame()
//结构体大小计算:字节对齐原则
AVPacket *packet = av_malloc(sizeof(AVPacket));
//3.2 解码一帧视频压缩数据->进行解码(作用:用于解码操作)
//开辟一块内存空间
AVFrame * avframe_in = av_frame_alloc();
int decode_result = 0;
struct SwsContext *swsContext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL, NULL, NULL);
//创建一个yuv420视频像素数据格式缓冲区(一帧数据)
AVFrame *avframe_yuv_420p = av_frame_alloc();
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
//开辟一块内存空间
uint8_t *out_buffer =(uint8_t *) av_malloc(buffer_size);
//向avframe_yuv420p->填充数据
//参数一:目标->填充数据(avframe_yuv420p)
//参数二:目标->每一行大小
//参数三:原始数据
//参数四:目标->格式类型
//参数五:宽
//参数六:高
//参数七:字节对齐方式
av_image_fill_arrays(avframe_yuv_420p->data,
avframe_yuv_420p->linesize,
out_buffer, AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height, 1);
int y_size, u_size, v_size;
FILE *file_yuv420p = fopen(outfilePath, "wb+");
if(file_yuv420p == NULL){
NSLog(@"输出文件打开失败");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, packet) >= 0) {
//>=:读取到了
//<0:读取错误或者读取完毕
//2、是否是我们的视频流
if(packet->stream_index == av_stream_index){
//第七步:解码
//学习一下C基础,结构体
//3、解码一帧压缩数据->得到视频像素数据->yuv格式
//采用新的API
//3.1 发送一帧视频压缩数据
avcodec_send_packet(avcodec_context, packet);
decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
if(decode_result == 0){
//解码成功
//4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
//视频像素数据格式很多种类型: yuv420P、yuv422p、yuv444p等等...
//保证:我的解码后的视频像素数据格式统一为yuv420P->通用的格式
//进行类型转换: 将解码出来的视频像素点数据格式->统一转类型为yuv420P
//sws_scale作用:进行类型转换的
//参数一:视频像素数据格式上下文
//参数二:原来的视频像素数据格式->输入数据
//参数三:原来的视频像素数据格式->输入画面每一行大小
//参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)
//参数五:原来的视频像素数据格式->输入数据行数
//参数六:转换类型后视频像素数据格式->输出数据
//参数七:转换类型后视频像素数据格式->输出画面每一行大小
sws_scale(swsContext,
(const uint8_t *const*)avframe_in->data,
avframe_in->linesize,
0,
avcodec_context->height,
avframe_yuv_420p->data,
avframe_yuv_420p->linesize);
//方式一:直接显示视频上面去
//方式二:写入yuv文件格式
//5、将yuv420p数据写入.yuv文件中
//5.1 计算YUV大小
//分析一下原理?
//Y表示:亮度
//UV表示:色度
//有规律
//YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)
//YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V)
y_size = avcodec_context->width * avcodec_context->height;
u_size = y_size / 4;
v_size = u_size;
//ptr:指向保存数据的指针;
//size:每个数据类型的大小;
//count:数据的个数;
//stream:文件指针函数返回写入数据的个数
fwrite(avframe_yuv_420p->data[0], 1, y_size, file_yuv420p);
fwrite(avframe_yuv_420p->data[1], 1, u_size, file_yuv420p);
fwrite(avframe_yuv_420p->data[2], 1, v_size, file_yuv420p);
current_index++;
NSLog(@"当前解码第%d帧", current_index);
}
}
}
av_packet_free(&packet);
fclose(file_yuv420p);
av_frame_free(&avframe_in);
av_frame_free(&avframe_yuv_420p);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
音频解码实现
+(void)ffmepgDecodeAudio:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
//第一步:组册组件
const char *infilePath = [jinfilePath UTF8String];
const char *outfilePath = [joutfilePath UTF8String];
av_register_all();
AVFormatContext *avformat_context = avformat_alloc_context();
//第二步:打开封装格式->打开文件
//参数一:封装格式上下文
//作用:保存整个视频信息(解码器、编码器等等...)
//信息:码率、帧率等...
int avformat_open_input_result = avformat_open_input(&avformat_context,
infilePath,
NULL,
NULL);
if (avformat_open_input_result != 0){
NSLog(@"打开文件失败");
return;
}
//第三步:查找视频流->拿到视频信息
//参数一:封装格式上下文
//参数二:指定默认配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
NULL);
if (avformat_find_stream_info_result < 0){
NSLog(@"查找失败");
return;
}
//第四步:查找音频解码器
//第一点:查找音频流索引位置
int av_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//判断是否是音频流
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
//AVMEDIA_TYPE_AUDIO->表示音频类型
av_stream_index = i;
break;
}
}
//第二点:获取音频解码器上下文
AVCodecContext * avcodec_context = avformat_context->streams[av_stream_index]->codec;
//第三点:获得音频解码器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (avcodec == NULL){
NSLog(@"查找音频解码器失败");
return;
}
//第五步:打开音频解码器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0) {
NSLog(@"打开音频解码器失败");
return;
}
//第六步:读取音频压缩数据->循环读取
//创建音频压缩数据帧
//音频压缩数据->acc格式、mp3格式
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//创建音频采样数据帧
AVFrame *frame_in = av_frame_alloc();
//音频采样上下文->开辟了一快内存空间->pcm格式等...
//设置参数
//参数一:音频采样数据上下文
//上下文:保存音频信息(记录)->目录
SwrContext *swr_context = swr_alloc();
//参数二:out_ch_layout->输出声道布局类型(立体声、环绕声、机器人等等...)
//立体声
int64_t out_ch_layout =AV_CH_LAYOUT_STEREO;
//参数三:out_sample_fmt->输出采样精度->编码 AV_SAMPLE_FMT_S16
enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
//参数四:out_sample_rate->输出采样率(44100HZ)
int out_sample_rate = 44100;
//参数五:in_ch_layout->输入声道布局类型
int64_t in_ch_layout = avcodec_context->channel_layout;
//参数六:in_sample_fmt->输入采样精度
enum AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
//参数七:in_sample_rate->输入采样率
int in_sample_rate = avcodec_context->sample_rate;
//参数八:log_offset->log日志->从那里开始统计
int log_offset = 0;
//参数九:log_ctx->log上下文
swr_alloc_set_opts(swr_context, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, log_offset, NULL);
swr_init(swr_context);
//输出缓冲区,
//输出音频采样数据
//缓冲区大小 = 采样率(44100HZ) * 采样精度(16位 = 2字节)
int MAX_AUDIO_SIZE = 44100 * 2;
uint8_t *out_buffer = (uint8_t *)malloc(sizeof(MAX_AUDIO_SIZE));
FILE *out_file_pcm = fopen(outfilePath, "wb");
if (out_file_pcm == NULL) {
NSLog(@"输出文件打开失败");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, packet) >= 0) {
//读取一帧音频压缩数据成功
//判定是否是音频流
if (packet->stream_index == av_stream_index) {
//第七步:音频解码
//1、发送一帧音频压缩数据包->音频压缩数据帧
avcodec_send_packet(avcodec_context, packet);
//2、解码一帧音频压缩数据包->得到->一帧音频采样数据->音频采样数据帧
int avcodec_receive_result = avcodec_receive_frame(avcodec_context, frame_in);
if (avcodec_receive_result == 0) {
//表示音频压缩数据解码成功
//3、类型转换(音频采样数据格式有很多种类型)
//我希望我们的音频采样数据格式->pcm格式->保证格式统一->输出PCM格式文件
//swr_convert:表示音频采样数据类型格式转换器
//参数一:音频采样数据上下文
//参数二:输出音频采样数据
//参数三:输出音频采样数据->大小
//参数四:输入音频采样数据
//参数五:输入音频采样数据->大小
swr_convert(swr_context, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **)frame_in->data, frame_in->nb_samples);
//tian
//输出声道数量 av_get_channel_layout_nb_channels(out_ch_layout)
int out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);
int out_Buffer_size = av_samples_get_buffer_size(NULL,
out_nb_channels,
frame_in->nb_samples,
out_sample_fmt, 1);
fwrite(out_buffer, 1, out_Buffer_size, out_file_pcm);
current_index++;
NSLog(@"当前音频解码第%d帧",current_index);
}
}
}
//第八步:释放内存资源,关闭解码器
av_packet_free(&packet);
fclose(out_file_pcm);
av_frame_free(&frame_in);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
#音频解码实现
+(void)ffmepgDecodeAudio:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
//第一步:组册组件
const char *infilePath = [jinfilePath UTF8String];
const char *outfilePath = [joutfilePath UTF8String];
av_register_all();
AVFormatContext *avformat_context = avformat_alloc_context();
//第二步:打开封装格式->打开文件
//参数一:封装格式上下文
//作用:保存整个视频信息(解码器、编码器等等...)
//信息:码率、帧率等...
int avformat_open_input_result = avformat_open_input(&avformat_context,
infilePath,
NULL,
NULL);
if (avformat_open_input_result != 0){
NSLog(@"打开文件失败");
return;
}
//第三步:查找视频流->拿到视频信息
//参数一:封装格式上下文
//参数二:指定默认配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
NULL);
if (avformat_find_stream_info_result < 0){
NSLog(@"查找失败");
return;
}
//第四步:查找音频解码器
//第一点:查找音频流索引位置
int av_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//判断是否是音频流
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
//AVMEDIA_TYPE_AUDIO->表示音频类型
av_stream_index = i;
break;
}
}
//第二点:获取音频解码器上下文
AVCodecContext * avcodec_context = avformat_context->streams[av_stream_index]->codec;
//第三点:获得音频解码器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (avcodec == NULL){
NSLog(@"查找音频解码器失败");
return;
}
//第五步:打开音频解码器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0) {
NSLog(@"打开音频解码器失败");
return;
}
//第六步:读取音频压缩数据->循环读取
//创建音频压缩数据帧
//音频压缩数据->acc格式、mp3格式
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//创建音频采样数据帧
AVFrame *frame_in = av_frame_alloc();
//音频采样上下文->开辟了一快内存空间->pcm格式等...
//设置参数
//参数一:音频采样数据上下文
//上下文:保存音频信息(记录)->目录
SwrContext *swr_context = swr_alloc();
//参数二:out_ch_layout->输出声道布局类型(立体声、环绕声、机器人等等...)
//立体声
int64_t out_ch_layout =AV_CH_LAYOUT_STEREO;
//参数三:out_sample_fmt->输出采样精度->编码 AV_SAMPLE_FMT_S16
enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
//参数四:out_sample_rate->输出采样率(44100HZ)
int out_sample_rate = 44100;
//参数五:in_ch_layout->输入声道布局类型
int64_t in_ch_layout = avcodec_context->channel_layout;
//参数六:in_sample_fmt->输入采样精度
enum AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
//参数七:in_sample_rate->输入采样率
int in_sample_rate = avcodec_context->sample_rate;
//参数八:log_offset->log日志->从那里开始统计
int log_offset = 0;
//参数九:log_ctx->log上下文
swr_alloc_set_opts(swr_context, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, log_offset, NULL);
swr_init(swr_context);
//输出缓冲区,
//输出音频采样数据
//缓冲区大小 = 采样率(44100HZ) * 采样精度(16位 = 2字节)
int MAX_AUDIO_SIZE = 44100 * 2;
uint8_t *out_buffer = (uint8_t *)malloc(sizeof(MAX_AUDIO_SIZE));
FILE *out_file_pcm = fopen(outfilePath, "wb");
if (out_file_pcm == NULL) {
NSLog(@"输出文件打开失败");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, packet) >= 0) {
//读取一帧音频压缩数据成功
//判定是否是音频流
if (packet->stream_index == av_stream_index) {
//第七步:音频解码
//1、发送一帧音频压缩数据包->音频压缩数据帧
avcodec_send_packet(avcodec_context, packet);
//2、解码一帧音频压缩数据包->得到->一帧音频采样数据->音频采样数据帧
int avcodec_receive_result = avcodec_receive_frame(avcodec_context, frame_in);
if (avcodec_receive_result == 0) {
//表示音频压缩数据解码成功
//3、类型转换(音频采样数据格式有很多种类型)
//我希望我们的音频采样数据格式->pcm格式->保证格式统一->输出PCM格式文件
//swr_convert:表示音频采样数据类型格式转换器
//参数一:音频采样数据上下文
//参数二:输出音频采样数据
//参数三:输出音频采样数据->大小
//参数四:输入音频采样数据
//参数五:输入音频采样数据->大小
swr_convert(swr_context, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **)frame_in->data, frame_in->nb_samples);
//tian
//输出声道数量 av_get_channel_layout_nb_channels(out_ch_layout)
int out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);
int out_Buffer_size = av_samples_get_buffer_size(NULL,
out_nb_channels,
frame_in->nb_samples,
out_sample_fmt, 1);
fwrite(out_buffer, 1, out_Buffer_size, out_file_pcm);
current_index++;
NSLog(@"当前音频解码第%d帧",current_index);
}
}
}
//第八步:释放内存资源,关闭解码器
av_packet_free(&packet);
fclose(out_file_pcm);
av_frame_free(&frame_in);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
在ViewController中调用
NSString *inpath = [[NSBundle mainBundle] pathForResource:@"Test" ofType:@"mov"];
NSString *outpath = [NSString stringWithFormat:@"%@/Documents/Test1.yuv",NSHomeDirectory()];
NSFileManager *manager = [NSFileManager defaultManager];
if(![manager fileExistsAtPath:outpath]){
BOOL b= [manager createFileAtPath:outpath contents:nil attributes:nil];
NSLog(@"%d",[manager fileExistsAtPath:outpath]);
}else{
long long l = [[manager attributesOfItemAtPath:outpath error:nil] fileSize];
NSLog(@"%lld",l);
NSLog(@"存在文件");
}
ffplay播放原始格式的音频文件(PCM)和视频文件(YUV)
播放原始视频yuv数据, 以1280*720分辨率的xxx.yuv为例
$ ffplay -f rawvideo -video_size 1280x720 xxx.yuv
播放16kHz 1单声道 采样精度(编码)16bit的xxx.pcm的PCM文件为例
$ ffplay -ar 16000 -channels 1 -f s16le -i xxx.pcm
网友评论