zoukankan      html  css  js  c++  java
  • FFmpeg YUV视频序列编码为视频

    上一篇已经写了如何配置好开发环境,这次就先小试牛刀,来个视频的编码。搞视频处理的朋友肯定比较熟悉YUV视频序列,很多测试库提供的视频数据都是YUV视频序列,我们这里就用用YUV视频序列来做视频。关于YUV视频序列,我就不多讲了,可以看书学习,通常的视频序列都是YUV420格式的。

    步骤也就那几步,添加视频流,打开编码器,开辟相应的内存空间,然后就可以打开YUV序列逐帧写入数据了,so easy!记得最后要做好文件的关闭和内存的释放,因为FFmpeg是c风格的(不知道新版本是否是c++风格的),这些工作都需要自己做好啊。过多的说明是没用的,直接上代码:

    这里我补充一下,大多数的视频格式好像只支持YUV格式的视频帧AVFrame,我试图直接把RGB的视频序列直接编码到视频这条路好像走不通,都需要把RGB的视频帧再转成YUV视频帧才行,不知道高手有没有其他高见。

    #include <stdio.h>
    #include <string.h>
    
    extern "C"
    {
    #include <libavcodecavcodec.h>
    #include <libavformatavformat.h>
    #include <libswscaleswscale.h>
    };
    
    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<oc->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
    ");
            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; i<pFormatCtx->nb_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!
    ");
    //         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!
    ");
            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 <stdio.h>
    #include <stdlib.h>
    
    extern "C"
    {
    #include <libavcodecavcodec.h>
    #include <libavformatavformat.h>
    #include <libswscaleswscale.h>
    }
    
    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
    ");
        if (av_find_stream_info(pInFmtCtx) < 0)    //获取文件内音视频流的信息
            printf("av_find_stream_info error
    ");
    
        unsigned int j;
        // Find the first audio stream
    
        int audioStream = -1;
        for (j=0; j<pInFmtCtx->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
    ");
            return 0; // Didn't find a audio stream
        }
        printf("audio stream num: %d
    ",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
    ");
            return -1 ; // Codec not found
        }
    
        if(avcodec_open(pInCodecCtx, pInCodec)<0)//将两者结合以便在下面的解码函数中调用pInCodec中的对应解码函数
        {
            printf("error avcodec_open failed.
    ");
            return -1; // Could not open codec
    
        }
    
        static AVPacket packet;
    
        printf(" bit_rate = %d 
    ", pInCodecCtx->bit_rate);
        printf(" sample_rate = %d 
    ", pInCodecCtx->sample_rate);
        printf(" channels = %d 
    ", pInCodecCtx->channels);
        printf(" code_name = %s 
    ", pInCodecCtx->codec->name);
        printf(" block_align = %d
    ",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.
    ");
                        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
    ",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; i<oc->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);
        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 -> 音视频处理)

  • 相关阅读:
    (转)WCF中的REST是什么
    DrpList
    IIS代码管理(1):遍历应用程序池和属性
    rdp,ListBox,Drp
    在.NET中杀死Word,Excel等进程
    IIS代码管理(2):创建应用程序池和属性
    防止用户重复登录
    asp.net2.0的几种自动生成脚本的原理以及应用
    工厂模式new问题
    我们需要什么样的字段类型?
  • 原文地址:https://www.cnblogs.com/welhzh/p/4218752.html
Copyright © 2011-2022 走看看