您的位置:首页 > 其它

关于视频H264编解码的应用实现

2018-01-25 13:47 344 查看
http://blog.csdn.net/scalerzhangjie/article/details/8273410

项目要用到视频编解码,最近半个月都在搞,说实话真是走了很多弯路,浪费了很多时间。将自己的最终成果记录于此,期望会给其他人提供些许帮助。  

参考教程:

http://ffmpeg.org/trac/ffmpeg/wiki/UbuntuCompilationGuide安装ffmpeg和x264,官方权威教程(注意不要用命令行安装,会少很多库的。编译安装最保险)

http://blog.csdn.net/zgyulongfei/article/details/7526249采集与编码的教程

http://www.cnblogs.com/fojian/archive/2012/09/01/2666627.html编码的好文章

http://my.oschina.net/u/555701/blog/56616?p=2#comments-解码的好文章 

整体过程流程如下:



 

 

 

显而易见,整个过程分为三个部分:采集、编码、解码。

1.        采集视频

我是利用USB摄像头采集视频的,我的摄像头只支持YUV422格式的图像采集,因为x264编码库只能编码YUV420P(planar)格式,因此在采集到yuv422格式的图像数据后要变换成yuv420p格式。

采集视频使用官方的那个采集程序,稍加修改即可,具体点说就是修改

static void  process_image (const char * p) ;函数

参数p指向一帧采集图像的yuv数据。

 

关于YUV格式和RGB格式,网上有很多教程。

在这儿,我讲一下自己的理解。

假设有一幅4*4分辨率的图片,如下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 

每个像素是由YUV数据构成,假设如下:

 

Y1
U1
V1
Y2
U2
V2
Y3
U3
V3
Y4
U4
V4
Y5
U5
V5
Y6
U6
V6
Y7
U7
V7
Y8
U8
V8
Y9
U9
V9
Y10
U10
V10
Y11
U11
V11
Y12
U12
V12
Y13
U13
V13
Y14
U14
V14
Y15
U15
V15
Y16
U16
V16
 

YUV422图像是这样的,每个像素采集Y,UV每隔两个像素采集一次:



 

Packed格式的YUV420是这样的,每个像素采集Y,UV隔行采集,每行是每两个像素采集一次:



 

以上几种格式存储就是按照从左到右,从上到下顺序存储的。

我想要得到是planar格式的YUV420,即在一段连续的内存中,先存储所有的Y,接着是所有的U,最后是所有的V。

修改后的 process_image函数如下:

 

[cpp] view
plain copy
 
 





static void  

process_image                   (const char *           p)  

{  

        //fputc ('.', stdout);  

    //convert yuv422 to yuv420p  

            char *y=yuv420p;  

            char *u=&yuv420p[IMAGE_WIDTH*IMAGE_HEIGHT];  

            char *v=&yuv420p[IMAGE_WIDTH*IMAGE_HEIGHT+IMAGE_WIDTH*IMAGE_HEIGHT/4];  

  

            int i=0,j=0,l=0;  

            for(j=0;j<IMAGE_HEIGHT;j++)  

                for(i=0;i<IMAGE_WIDTH*2;i++,l++){  

  

                    if(j%2==0){//even line to sample U-Chriminance  

                        if(l==1){//sample U-Chriminance  

                            *u=p[j*IMAGE_WIDTH*2+i];  

                            u++;  

                         }  

                         else if(l==3){//abandon V-Chroma  

                            l=-1;  

                            continue;  

  

                         }  

                         else{  

                            *y=p[j*IMAGE_WIDTH*2+i];  

                            ++y;  

                         }  

                    }  

  

                    else if(j%2==1){//odd lines to sample  V-Chroma  

                        if(l==1){  

                            continue;  

                        }  

                        else if(l==3){  

                            l=-1;  

                            *v=p[j*IMAGE_WIDTH*2+i];  

                            ++v;  

                        }  

                        else {  

                            *y=p[j*IMAGE_WIDTH*2+i];  

                            ++y;  

                        }  

  

                    }  

  

                }  

  

            fwrite(yuv420p,IMAGE_WIDTH*IMAGE_HEIGHT*3>>1,1,fp_yuv420p);  

  

            fflush (stdout);  

          

}  

 

2.编码

采用x264编码库编码yuv420p文件。

程序如下:

[cpp] view
plain copy
 
 





#include <stdint.h>  

#include <x264.h>  

#include <stdio.h>  

#include <unistd.h>  

#include <fcntl.h>  

#include <stdlib.h>  

#include <string.h>  

  

#define DEBUG 0  

  

#define CLEAR(x) (memset((&x),0,sizeof(x)))  

#define IMAGE_WIDTH   320  

#define IMAGE_HEIGHT  240  

#define ENCODER_PRESET "veryfast"  

#define ENCODER_TUNE   "zerolatency"  

#define ENCODER_PROFILE  "baseline"  

#define ENCODER_COLORSPACE X264_CSP_I420  

  

typedef struct my_x264_encoder{  

    x264_param_t  * x264_parameter;  

    char parameter_preset[20];  

    char parameter_tune[20];  

    char parameter_profile[20];  

    x264_t  * x264_encoder;  

    x264_picture_t * yuv420p_picture;  

    long colorspace;  

    unsigned char *yuv;  

    x264_nal_t * nal;  

} my_x264_encoder;  

  

char *read_filename="yuv420p.yuv";  

char *write_filename="encode.h264";  

  

int  

main(int argc ,char **argv){  

    int ret;  

    int fd_read,fd_write;  

    my_x264_encoder * encoder=(my_x264_encoder *)malloc(sizeof(my_x264_encoder));  

    if(!encoder){  

        printf("cannot malloc my_x264_encoder !\n");  

        exit(EXIT_FAILURE);  

    }  

    CLEAR(*encoder);  

  

  

    /**************************************************************************** 

     * Advanced parameter handling functions 

     ****************************************************************************/  

  

    /* These functions expose the full power of x264's preset-tune-profile system for 

     * easy adjustment of large numbers //free(encoder->yuv420p_picture);of internal parameters. 

     * 

     * In order to replicate x264CLI's option handling, these functions MUST be called 

     * in the following order: 

     * 1) x264_param_default_preset 

     * 2) Custom user options (via param_parse or directly assigned variables) 

     * 3) x264_param_apply_fastfirstpass 

     * 4) x264_param_apply_profile 

     * 

     * Additionally, x264CLI does not apply step 3 if the preset chosen is "placebo" 

     * or --slow-firstpass is set. */  

    strcpy(encoder->parameter_preset,ENCODER_PRESET);  

    strcpy(encoder->parameter_tune,ENCODER_TUNE);  

  

    encoder->x264_parameter=(x264_param_t *)malloc(sizeof(x264_param_t));  

    if(!encoder->x264_parameter){  

        printf("malloc x264_parameter error!\n");  

        exit(EXIT_FAILURE);  

    }  

    CLEAR(*(encoder->x264_parameter));  

    x264_param_default(encoder->x264_parameter);  

  

    if((ret=x264_param_default_preset(encoder->x264_parameter,encoder->parameter_preset,encoder->parameter_tune))<0){  

        printf("x264_param_default_preset error!\n");  

        exit(EXIT_FAILURE);  

    }  

  

    encoder->x264_parameter->i_fps_den         =1;  

    encoder->x264_parameter->i_fps_num         =25;  

    encoder->x264_parameter->i_width       =IMAGE_WIDTH;  

    encoder->x264_parameter->i_height      =IMAGE_HEIGHT;  

    encoder->x264_parameter->i_threads         =1;  

    encoder->x264_parameter->i_keyint_max    =25;  

    encoder->x264_parameter->b_intra_refresh =1;  

    encoder->x264_parameter->b_annexb      =1;  

  

    strcpy(encoder->parameter_profile,ENCODER_PROFILE);  

    if((ret=x264_param_apply_profile(encoder->x264_parameter,encoder->parameter_profile))<0){  

        printf("x264_param_apply_profile error!\n");  

        exit(EXIT_FAILURE);  

    }  

  

#if DEBUG  

    printf("Line --------%d\n",__LINE__);  

#endif  

  

    encoder->x264_encoder=x264_encoder_open(encoder->x264_parameter);  

  

    encoder->colorspace=ENCODER_COLORSPACE;  

  

#if DEBUG  

    printf("Line --------%d\n",__LINE__);  

#endif  

  

    encoder->yuv420p_picture=(x264_picture_t *)malloc(sizeof(x264_picture_t ));  

    if(!encoder->yuv420p_picture){  

        printf("malloc encoder->yuv420p_picture error!\n");  

        exit(EXIT_FAILURE);  

    }  

    if((ret=x264_picture_alloc(encoder->yuv420p_picture,encoder->colorspace,IMAGE_WIDTH,IMAGE_HEIGHT))<0){  

        printf("ret=%d\n",ret);  

        printf("x264_picture_alloc error!\n");  

        exit(EXIT_FAILURE);  

    }  

  

    encoder->yuv420p_picture->img.i_csp=encoder->colorspace;  

    encoder->yuv420p_picture->img.i_plane=3;  

    encoder->yuv420p_picture->i_type=X264_TYPE_AUTO;  

  

  

  

#if DEBUG  

    printf("Line --------%d\n",__LINE__);  

#endif  

  

    encoder->yuv=(uint8_t *)malloc(IMAGE_WIDTH*IMAGE_HEIGHT*3/2);  

    if(!encoder->yuv){  

        printf("malloc yuv error!\n");  

        exit(EXIT_FAILURE);  

    }  

    CLEAR(*(encoder->yuv));  

  

#if DEBUG  

    printf("Line --------%d\n",__LINE__);  

#endif  

  

    encoder->yuv420p_picture->img.plane[0]=encoder->yuv;  

    encoder->yuv420p_picture->img.plane[1]=encoder->yuv+IMAGE_WIDTH*IMAGE_HEIGHT;  

    encoder->yuv420p_picture->img.plane[2]=encoder->yuv+IMAGE_WIDTH*IMAGE_HEIGHT+IMAGE_WIDTH*IMAGE_HEIGHT/4;  

  

    if((fd_read=open(read_filename,O_RDONLY))<0){  

        printf("cannot open input file!\n");  

        exit(EXIT_FAILURE);  

    }  

  

    if((fd_write=open(write_filename,O_WRONLY | O_APPEND | O_CREAT,0777))<0){  

        printf("cannot open output file!\n");  

        exit(EXIT_FAILURE);  

    }  

  

#if DEBUG  

    printf("Line --------%d\n",__LINE__);  

#endif  

    int n_nal;  

    x264_picture_t pic_out;  

    x264_nal_t *my_nal;  

    encoder->nal=(x264_nal_t *)malloc(sizeof(x264_nal_t ));  

    if(!encoder->nal){  

        printf("malloc x264_nal_t error!\n");  

        exit(EXIT_FAILURE);  

    }  

    CLEAR(*(encoder->nal));  

  

    while(read(fd_read,encoder->yuv,IMAGE_WIDTH*IMAGE_HEIGHT*3/2)>0){  

        encoder->yuv420p_picture->i_pts++;  

        if((ret=x264_encoder_encode(encoder->x264_encoder,&encoder->nal,&n_nal,encoder->yuv420p_picture,&pic_out))<0){  

            printf("x264_encoder_encode error!\n");  

            exit(EXIT_FAILURE);  

        }  

  

        unsigned int length=0;  

        for(my_nal=encoder->nal;my_nal<encoder->nal+n_nal;++my_nal){  

            write(fd_write,my_nal->p_payload,my_nal->i_payload);  

            length+=my_nal->i_payload;  

        }  

        printf("length=%d\n",length);  

    }  

  

    /*clean_up functions*/  

    //x264_picture_clean(encoder->yuv420p_picture);  

    //free(encoder->nal);//???? confused conflict with x264_encoder_close(encoder->x264_encoder);  

  

    free(encoder->yuv);  

    free(encoder->yuv420p_picture);  

    free(encoder->x264_parameter);  

    x264_encoder_close(encoder->x264_encoder);  

    free(encoder);  

    close(fd_read);  

    close(fd_write);  

  

    return 0;  

}  

3.        解码

利用ffmpeg进行解码

程序如下:

 

 

[cpp] view
plain copy
 
 





#include <stdio.h>  

#include <string.h>  

#include <stdlib.h>  

#include <fcntl.h>  

#include <unistd.h>  

#include <libavcodec/avcodec.h>  

#include <libavformat/avformat.h>  

#include <libavutil/mathematics.h>  

  

#define DECODED_OUTPUT_FORMAT  AV_PIX_FMT_YUV420P  

#define INPUT_FILE_NAME "encode.h264"  

#define OUTPUT_FILE_NAME "decode.yuv"  

#define IMAGE_WIDTH  320  

#define IMAGE_HEIGHT 240  

  

void  

error_handle(const char *errorInfo ){  

    printf("%s error!\n",errorInfo);  

    exit(EXIT_FAILURE);  

}  

  

  

int  

main(int argc,char ** argv){  

    int  write_fd,ret,videoStream;  

    AVFormatContext * formatContext=NULL;  

    AVCodec * codec;  

    AVCodecContext * codecContext;  

    AVFrame * decodedFrame;  

    AVPacket packet;  

    uint8_t *decodedBuffer;  

    unsigned int decodedBufferSize;  

    int finishedFrame;  

  

  

    av_register_all();  

  

  

    write_fd=open(OUTPUT_FILE_NAME,O_RDWR | O_CREAT,0666);  

    if(write_fd<0){  

        perror("open");  

        exit(1);  

    }  

  

    ret=avformat_open_input(&formatContext, INPUT_FILE_NAME, NULL,NULL);  

    if(ret<0)  

        error_handle("avformat_open_input error");  

  

    ret=avformat_find_stream_info(formatContext,NULL);  

    if(ret<0)  

        error_handle("av_find_stream_info");  

  

  

    videoStream=0;  

    codecContext=formatContext->streams[videoStream]->codec;  

  

    codec=avcodec_find_decoder(AV_CODEC_ID_H264);  

    if(codec==NULL)  

        error_handle("avcodec_find_decoder error!\n");  

  

    ret=avcodec_open2(codecContext,codec,NULL);  

    if(ret<0)  

        error_handle("avcodec_open2");  

  

    decodedFrame=avcodec_alloc_frame();  

    if(!decodedFrame)  

        error_handle("avcodec_alloc_frame!");  

  

    decodedBufferSize=avpicture_get_size(DECODED_OUTPUT_FORMAT,IMAGE_WIDTH,IMAGE_HEIGHT);  

    decodedBuffer=(uint8_t *)malloc(decodedBufferSize);  

    if(!decodedBuffer)  

        error_handle("malloc decodedBuffer error!");  

  

    av_init_packet(&packet);  

    while(av_read_frame(formatContext,&packet)>=0){  

            ret=avcodec_decode_video2(codecContext,decodedFrame,&finishedFrame,&packet);  

            if(ret<0)  

                error_handle("avcodec_decode_video2 error!");  

            if(finishedFrame){  

                avpicture_layout((AVPicture*)decodedFrame,DECODED_OUTPUT_FORMAT,IMAGE_WIDTH,IMAGE_HEIGHT,decodedBuffer,decodedBufferSize);  

                ret=write(write_fd,decodedBuffer,decodedBufferSize);  

                if(ret<0)  

                    error_handle("write yuv stream error!");  

            }  

  

        av_free_packet(&packet);  

    }  

  

    while(1){  

        packet.data=NULL;  

        packet.size=0;  

        ret=avcodec_decode_video2(codecContext,decodedFrame,&finishedFrame,&packet);  

        if(ret<=0 && (finishedFrame<=0))  

            break;  

        if(finishedFrame){  

            avpicture_layout((AVPicture*)decodedFrame,DECODED_OUTPUT_FORMAT,IMAGE_WIDTH,IMAGE_HEIGHT,decodedBuffer,decodedBufferSize);  

            ret=write(write_fd,decodedBuffer,decodedBufferSize);  

            if(ret<0)  

                error_handle("write yuv stream error!");  

        }  

  

        av_free_packet(&packet);  

    }  

  

  

    avformat_close_input(&formatContext);  

    free(decodedBuffer);  

    av_free(decodedFrame);  

    avcodec_close(codecContext);  

  

    return 0;  

}  

 

 

 

结果:

1.      利用USB摄像头采集的YUV420P,大小11.0MB,可以用pyuv播放器正常播放。

2.      编码后的文件encode.h264,大小262.4kb,可用vlc播放器正常播放。

3.      解码后的文件decode.yuv,大小11.0MB,可以用pyuv播放器正常播放。

 

相关文件在我的资源里,里面包含:

1.      采集、编码、解码程序、对应的可执行程序和Makefile文件;

2.      Pyuv播放器(用于XP)

3.      实验文件-yuv420p.yuv 、encode.h264、 decode.yuv

4.      相关参考文档pdf版本

 

 





欢迎批评指正!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: