上一篇已经写了如何配置好开发环境,这次就先小试牛刀,来个视频的编码。搞视频处理的朋友肯定比较熟悉YUV视频序列,很多测试库提供的视频数据都是YUV视频序列,我们这里就用用YUV视频序列来做视频。关于YUV视频序列,我就不多讲了,可以看书学习,通常的视频序列都是YUV420格式的。
步骤也就那几步,添加视频流,打开编码器,开辟相应的内存空间,然后就可以打开YUV序列逐帧写入数据了,so easy!记得最后要做好文件的关闭和内存的释放,因为FFmpeg是c风格的(不知道新版本是否是c++风格的),这些工作都需要自己做好啊。过多的说明是没用的,直接上代码:
这里我补充一下,大多数的视频格式好像只支持YUV格式的视频帧AVFrame,我试图直接把RGB的视频序列直接编码到视频这条路好像走不通,都需要把RGB的视频帧再转成YUV视频帧才行,不知道高手有没有其他高见。
#include#include <string.h> extern "C" { #include #include #include }; void main(int argc, char ** argv) { AVFormatContext* oc; AVOutputFormat* fmt; AVStream* video_st; double video_pts; uint8_t* video_outbuf; uint8_t* picture_buf; AVFrame* picture; // AVFrame* pictureRGB; int size; int ret; int video_outbuf_size; FILE *fin = fopen("akiyo_qcif.yuv", "rb"); //视频源文件 const char* filename = "test.mpg"; // const char* filename; // filename = argv[1]; av_register_all(); // avcodec_init(); // 初始化codec库 // avcodec_register_all(); // 注册编码器 fmt = guess_format(NULL, filename, NULL); oc = av_alloc_format_context(); oc->oformat = fmt; snprintf(oc->filename, sizeof(oc->filename), "%s", filename); video_st = NULL; if (fmt->video_codec != CODEC_ID_NONE) { AVCodecContext* c; video_st = av_new_stream(oc, 0); c = video_st->codec; c->codec_id = fmt->video_codec; c->codec_type = CODEC_TYPE_VIDEO; c->bit_rate = 400000; c->width = 176; c->height = 144; c->time_base.num = 1; c->time_base.den = 25; c->gop_size = 12; c->pix_fmt = PIX_FMT_YUV420P; if (c->codec_id == CODEC_ID_MPEG2VIDEO) { c->max_b_frames = 2; } if (c->codec_id == CODEC_ID_MPEG1VIDEO) { c->mb_decision = 2; } if (!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp")) { c->flags |= CODEC_FLAG_GLOBAL_HEADER; } } if (av_set_parameters(oc, NULL)<0) { return; } dump_format(oc, 0, filename, 1); if (video_st) { AVCodecContext* c; AVCodec* codec; c = video_st->codec; codec = avcodec_find_encoder(c->codec_id); if (!codec) { return; } if (avcodec_open(c, codec) < 0) { return; } if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { video_outbuf_size = 200000; video_outbuf = (uint8_t*)av_malloc(video_outbuf_size); } picture = avcodec_alloc_frame(); size = avpicture_get_size(c->pix_fmt, c->width, c->height); picture_buf = (uint8_t*)av_malloc(size); if (!picture_buf) { av_free(picture); } avpicture_fill((AVPicture*)picture, picture_buf, c->pix_fmt, c->width, c->height); } if (!(fmt->flags & AVFMT_NOFILE)) { if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) { return; } } av_write_header(oc); for (int i=0; i<300; i++) { if (video_st) { video_pts = (double)(video_st->pts.val * video_st->time_base.num / video_st->time_base.den); } else { video_pts = 0.0; } if (!video_st/* || video_pts >= 5.0*/) { break; } AVCodecContext* c; c = video_st->codec; size = c->width * c->height; if (fread(picture_buf, 1, size*3/2, fin) < 0) { break; } picture->data[0] = picture_buf; // 亮度 picture->data[1] = picture_buf+ size; // 色度 picture->data[2] = picture_buf+ size*5/4; // 色度 // 如果是rgb序列,可能需要如下代码 // SwsContext* img_convert_ctx; // img_convert_ctx = sws_getContext(c->width, c->height, PIX_FMT_RGB24, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL); // sws_scale(img_convert_ctx, pictureRGB->data, pictureRGB->linesize, 0, c->height, picture->data, picture->linesize); if (oc->oformat->flags & AVFMT_RAWPICTURE) { AVPacket pkt; av_init_packet(&pkt); pkt.flags |= PKT_FLAG_KEY; pkt.stream_index = video_st->index; pkt.data = (uint8_t*)picture; pkt.size = sizeof(AVPicture); ret = av_write_frame(oc, &pkt); } else { int out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture); if (out_size > 0) { AVPacket pkt; av_init_packet(&pkt); pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base); if (c->coded_frame->key_frame) { pkt.flags |= PKT_FLAG_KEY; } pkt.stream_index = video_st->index; pkt.data = video_outbuf; pkt.size = out_size; ret = av_write_frame(oc, &pkt); } } } if (video_st) { avcodec_close(video_st->codec); // av_free(picture->data[0]); av_free(picture); av_free(video_outbuf); // av_free(picture_buf); } av_write_trailer(oc); for (int i=0; i nb_streams; i++) { av_freep(&oc->streams[i]->codec); av_freep(&oc->streams[i]); } if (!(fmt->flags & AVFMT_NOFILE)) { url_fclose(oc->pb); } av_free(oc); }
上一篇介绍了视频编码的小例子,视频解码跟编码差不多,只是要在视频文件中寻找视频流,找到后对流逐帧解码,就这样简单。闲言少叙,上code:
int main(int argc, char *argv[]) { AVFormatContext *pFormatCtx; int i, videoStream; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame; AVFrame *pFrameRGB; AVPacket packet; int frameFinished; int numBytes; uint8_t *buffer; struct SwsContext *img_convert_ctx; if(argc < 2) { printf("Please provide a movie file\n"); return -1; } // Register all formats and codecs // 初始化ffmpeg库 av_register_all(); // Open video file if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0) return -1; // Couldn't open file // Retrieve stream information // 查找文件的流信息 if(av_find_stream_info(pFormatCtx)<0) return -1; // Couldn't find stream information // Dump information about file onto standard error // dump只是一个调试函数,输出文件的音、视频流的基本信息:帧率、分辨率、音频采样等等 dump_format(pFormatCtx, 0, argv[1], 0); // Find the first video stream // 遍历文件的流,找到第一个视频流,并记录流的编码信息 videoStream=-1; for(i=0; inb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) { videoStream=i; break; } } if(videoStream==-1) return -1; // Didn't find a video stream // Get a pointer to the codec context for the video stream // 得到视频流编码的上下文指针 pCodecCtx=pFormatCtx->streams[videoStream]->codec; // construct the scale context, conversing to PIX_FMT_RGB24 // 根据编码信息设置渲染格式 img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL); if(img_convert_ctx == NULL) { fprintf(stderr, "Cannot initialize the conversion context!\n"); // exit(1); return -1; } // Find the decoder for the video stream // 在库里面查找支持该格式的解码器 pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Open codec // 打开解码器 if(avcodec_open(pCodecCtx, pCodec)<0) return -1; // Could not open codec // Allocate video frame // 分配一个帧指针,指向解码后的原始帧 pFrame=avcodec_alloc_frame(); // Allocate an AVFrame structure // 分配一个帧指针,指向存放转换成rgb后的帧 pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB==NULL) return -1; // Determine required buffer size and allocate buffer numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); // buffer = new uint8_t[numBytes]; // Assign appropriate parts of buffer to image planes in pFrameRGB // Note that pFrameRGB is an AVFrame, but AVFrame is a superset // of AVPicture // 给pFrameRGB帧附加上分配的内存 avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); // Read frames and save first five frames to disk i=0; while(av_read_frame(pFormatCtx, &packet)>=0) // 读取一个帧 { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame // 解码该帧 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB // img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, // (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, // pCodecCtx->height); // 把该帧转换成rgb // 如果只提取关键帧,加上这句 // if (pFrame->key_frame == 1) sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // Save the frame to disk // 保存前5帧 if(++i<=5) { // char pic[200]; // sprintf(pic,"pic%d.bmp",i); // av_create_bmp(pic, pFrameRGB->data[0], pCodecCtx->width, pCodecCtx->height, 24); SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } } } // Free the packet that was allocated by av_read_frame // 释放读取的帧内存 av_free_packet(&packet); } // Free the RGB image av_free(buffer); av_free(pFrameRGB); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); // Close the video file av_close_input_file(pFormatCtx); return 0; }
FFmpeg浅尝辄止(四)——音频的解码和编码
音频和视频其实是一样的,在文件中寻找音频流,然后解压出来,得到音频帧的数据,同样也可以按照设定的编码格式进行压缩,我这里把音频的解码和编码做成了两个工程,也是直接上代码:
#include#include extern "C" { #include #include #include } int main(char arg,char *argv[]) { char *filename = argv[1]; av_register_all(); //注册所有可解码类型 AVFormatContext *pInFmtCtx=NULL; //文件格式 AVCodecContext *pInCodecCtx=NULL; //编码格式 if (av_open_input_file(&pInFmtCtx, filename, NULL, 0, NULL)!=0) //获取文件格式 printf("av_open_input_file error\n"); if (av_find_stream_info(pInFmtCtx) < 0) //获取文件内音视频流的信息 printf("av_find_stream_info error\n"); unsigned int j; // Find the first audio stream int audioStream = -1; for (j=0; j nb_streams; j++) //找到音频对应的stream { if (pInFmtCtx->streams[j]->codec->codec_type == CODEC_TYPE_AUDIO) { audioStream = j; break; } } if (audioStream == -1) { printf("input file has no audio stream\n"); return 0; // Didn't find a audio stream } printf("audio stream num: %d\n",audioStream); pInCodecCtx = pInFmtCtx->streams[audioStream]->codec; //音频的编码上下文 AVCodec *pInCodec = NULL; pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id); //根据编码ID找到用于解码的结构体 if (pInCodec == NULL) { printf("error no Codec found\n"); return -1 ; // Codec not found } if(avcodec_open(pInCodecCtx, pInCodec)<0)//将两者结合以便在下面的解码函数中调用pInCodec中的对应解码函数 { printf("error avcodec_open failed.\n"); return -1; // Could not open codec } static AVPacket packet; printf(" bit_rate = %d \r\n", pInCodecCtx->bit_rate); printf(" sample_rate = %d \r\n", pInCodecCtx->sample_rate); printf(" channels = %d \r\n", pInCodecCtx->channels); printf(" code_name = %s \r\n", pInCodecCtx->codec->name); printf(" block_align = %d\n",pInCodecCtx->block_align); uint8_t *pktdata; int pktsize; int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100; uint8_t * inbuf = (uint8_t *)malloc(out_size); FILE* pcm; pcm = fopen("result.pcm","wb"); long start = clock(); while (av_read_frame(pInFmtCtx, &packet) >= 0)//pInFmtCtx中调用对应格式的packet获取函数 { if(packet.stream_index==audioStream)//如果是音频 { pktdata = packet.data; pktsize = packet.size; while(pktsize>0) { out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100; //解码 int len = avcodec_decode_audio2(pInCodecCtx, (short*)inbuf, &out_size, pktdata, pktsize); if (len < 0) { printf("Error while decoding.\n"); break; } if(out_size > 0) { fwrite(inbuf,1,out_size,pcm);//pcm记录 fflush(pcm); } pktsize -= len; pktdata += len; } } av_free_packet(&packet); } long end = clock(); printf("cost time :%f\n",double(end-start)/(double)CLOCKS_PER_SEC); free(inbuf); fclose(pcm); if (pInCodecCtx!=NULL) { avcodec_close(pInCodecCtx); } av_close_input_file(pInFmtCtx); return 0; }
解码后的文件保存为result.pcm中,现在用这个文件压缩出新的音频文件,代码如下:
void main() { int16_t *samples; uint8_t *audio_outbuf; int audio_outbuf_size; int audio_input_frame_size; double audio_pts; const char* filename = "test.wav"; FILE *fin = fopen("result.pcm", "rb"); //音频源文件 AVOutputFormat *fmt; AVFormatContext *oc; AVStream * audio_st; av_register_all(); fmt = guess_format(NULL, filename, NULL); oc = av_alloc_format_context(); oc->oformat = fmt; snprintf(oc->filename, sizeof(oc->filename), "%s", filename); audio_st = NULL; if (fmt->audio_codec != CODEC_ID_NONE) { AVCodecContext *c; audio_st = av_new_stream(oc, 1); c = audio_st->codec; c->codec_id = fmt->audio_codec; c->codec_type = CODEC_TYPE_AUDIO; c->bit_rate = 128000; c->sample_rate = 44100; c->channels = 2; } if (av_set_parameters(oc, NULL) < 0) { return; } dump_format(oc, 0, filename, 1); if (audio_st) { AVCodecContext* c; AVCodec* codec; c = audio_st->codec; codec = avcodec_find_encoder(c->codec_id); avcodec_open(c, codec); audio_outbuf_size = 10000; audio_outbuf = (uint8_t*)av_malloc(audio_outbuf_size); if (c->frame_size <= 1) { audio_input_frame_size = audio_outbuf_size / c->channels; switch (audio_st->codec->codec_id) { case CODEC_ID_PCM_S16LE: case CODEC_ID_PCM_S16BE: case CODEC_ID_PCM_U16LE: case CODEC_ID_PCM_U16BE: audio_input_frame_size >>= 1; break; default: break; } } else { audio_input_frame_size = c->frame_size; } samples = (int16_t*)av_malloc(audio_input_frame_size*2*c->channels); } if (!fmt->flags & AVFMT_NOFILE) { if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) { return; } } av_write_header(oc); for (;;) { if (audio_st) { audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; } else { audio_pts = 0.0; } if (!audio_st || audio_pts >= 360.0) { break; } if (fread(samples, 1, audio_input_frame_size*2*audio_st->codec->channels, fin) <= 0) { break; } AVCodecContext* c; AVPacket pkt; av_init_packet(&pkt); c = audio_st->codec; pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, samples); pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st->time_base); pkt.flags |= PKT_FLAG_KEY; pkt.stream_index = audio_st->index; pkt.data = audio_outbuf; if (av_write_frame(oc, &pkt) != 0) { return; } } if (audio_st) { avcodec_close(audio_st->codec); av_free(samples); av_free(audio_outbuf); } av_write_trailer(oc); for (int i=0; inb_streams; i++) { av_freep(&oc->streams[i]->codec); av_freep(&oc->streams[i]); } if (!(fmt->flags & AVFMT_NOFILE)) { url_fclose(oc->pb); } av_free(oc); fclose(fin); }
对应的下载链接:
http://download.csdn.net/detail/yang_xian521/4398576
音频解码:http://download.csdn.net/detail/yang_xian521/4399219
音频编码:http://download.csdn.net/detail/yang_xian521/4399293
至此,我已经实现了视频的解码编码,音频的解码编码,相信有心人肯定可以在此基础上实现视频音频的同步压缩,不过要再看一些网上的资料,也是很轻松 的。至于视频的显示播放,大多数是要结合SDL实现,由于我只是浅尝辄止,只实现了图片的显示和简单的视频播放,比起我之前推荐的几个链接是弱爆了的,就 不写下去了,这个系列就到这里吧,希望能给像我一样的一些入门级选手点帮助。也欢迎大神来多多指教。
(附: 例子文件见360云盘的 所有文件-> work -> 音视频处理)