ffmpeg解码视频并输出到屏幕
ffmpeg解码视频并输出到屏幕
曾经花田错 发表于1年前
ffmpeg解码视频并输出到屏幕
  • 发表于 1年前
  • 阅读 109
  • 收藏 0
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

* 学习自雷神的博客和视频,图片来自他的课件,雷神博客:* 

http://blog.csdn.NET/leixiaohua1020/

 

ffmpeg基础知识

ffmpeg的库

  • avdecoc: 编解码
  • avformat: 封装格式的处理(mkv,mp4,avi)
  • swscale: 视频像素数据格式转换(常用于解码后视频的裁剪)
  • avutil: 工具库
  • avfilter: 滤镜特效处理
  • avdevice: 各种设备的输入输出
  • postproc: 后加工
  • swresample: 音频采样数据格式转换

ffmpeg的执行流程

        

  • avcodec_decode_open2()这个函数是解码函数, 最主要的一个函数。
  •   图中解码流程是:获取一个pakcet, 然后调用解码函数,把AVPacket结构中的data转换为AVFrame结构的data。
  • AVPacket结构存储一帧压缩的编码数据。
  • AVFrame结构存储一帧解码后的像素数据(对音频则是采样数据)。
  • AVFrame结构的元素data是双重指针,YUV数据来说包含data[0],data[1],data[2]分别存Y、U、V数据,注意每帧中U、V数据是Y数据的四分之一大小(对420P来说)。『Y:亮度数据, U,V:色差数据,由于人的眼睛对亮度更敏感,故而YUV数据中存更多的Y而减少UV的数据。当只有Y数据的时候,显示为黑白』 
  • 解码出来的数据可能函数无效像素。需要用sws_scale()函数处理。
  • 如图: 

         

 

ffmpeg 解码相关结构体

        

  • AVFormatContext是一个统筹全局的结构, 包含一些视频文件名,视频时长,视频码率等封装格式信息。 
  • AVInputFormat包含一些具体的视频格式信息,每种视频格式对应一个这个结构。
  •  一般来说视频文件有两个流:视频流和音频流。有几个流就有几个AVStream数据结构, 一般视频流的index==0(也有其他情况), AVStream在AVFormatContext中是一个双重指针。 
  • AVCodecContext 编解码器上下文结构体,保存音视频编解码相关信息。
  • AVCodec 每种视/音频编解码器(例如h264)对应一个该结构体。

SDL显示YUV图像流程:

        

  •  SDL_Surface就是使用SDL的时候弹出的那个窗口。
  • SDL_Overlay用于显示YUV数据。一个SDL_Overlay对应一帧YUV数据。
  • SDL_Rect用于确定SDL_Overlay显示的位置。

代码 

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

int main(int argc, char *argv[]) {
    AVFormatContext *pFormatCtx = NULL;
    int             i, videoStream;
    AVCodecContext  *pCodecCtx = NULL;
    AVCodec         *pCodec = NULL;
    AVFrame         *pFrame = NULL;
    AVPacket        packet;
    int             frameFinished;

    AVDictionary    *optionDict = NULL;
    struct SwsContext *sws_ctx = NULL;

    SDL_Overlay     *bmp = NULL;
    SDL_Surface     *screen = NULL;
    SDL_Rect        rect;
    SDL_Event       event;

    if(argc < 2){
        fprintf(stderr, "Usage: test <file> \n");
        exit(1);
    }

    av_register_all();

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
        fprintf(stderr,"Could not initialize SDL - %s " + *SDL_GetError());
        exit(1);
    }

    /*
    *打开一个文件
    */
    if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
        return -1;

    /*
	 *为pFormatCtx->streams填充上正确的信息
	 */
    if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
        return -1;

    /*
	 *手工调试函数,将文件信息在终端输出
	 */
    av_dump_format(pFormatCtx, 0, argv[1], 0);


    videoStream=-1;
	for ( i = 0; i < pFormatCtx->nb_streams; i++)
	  if(pFormatCtx -> streams[i] -> codec -> codec_type == AVMEDIA_TYPE_VIDEO) {
	    videoStream = i;
	    break;
	  }

	if(videoStream == -1)
	  return -1;

    /*
     *从 vedio stream 中获取对应的解码器上下文的指针
     */
    pCodecCtx = pFormatCtx -> streams[videoStream] -> codec;

    /*
     *根据 codec_id 找到对应的解码器
     */
    pCodec = avcodec_find_decoder(pCodecCtx -> codec_id);

    if(pCodec == NULL){
        fprintf(stderr, "Unsupported codec ! \n");
        return -1;
    }

    /*
     * 打开解码器
     */
    if(avcodec_open2(pCodecCtx, pCodec, &optionDict) <0 )
        return -1;

    /*
     * 为frame 申请内存
     */
    pFrame = av_frame_alloc();

    #ifdef __DARWIN__
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
    #else
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
    #endif // __DARWIN__

    if(!screen){
        fprintf(stderr, "SDL : could not set video mode - exiting \n");
        exit(1);
    }

    /*
     * 申请一个 overlay , 将 yuv数据给 screen
     */
    bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);

    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
                             AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);

    i = 0;
    while (av_read_frame(pFormatCtx, &packet) >= 0){

        if(packet.stream_index == videoStream){
            
            //为视频流解码
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

            if(frameFinished){
                SDL_LockYUVOverlay(bmp);
             
                AVPicture pict;
                pict.data[0] = bmp->pixels[0];
                pict.data[1] = bmp->pixels[2];
                pict.data[2] = bmp->pixels[1];

                pict.linesize[0] = bmp->pitches[0];
                pict.linesize[1] = bmp->pitches[2];
                pict.linesize[2] = bmp->pitches[1];

                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize,
                          0, pCodecCtx->height, pict.data, pict.linesize);

                SDL_UnlockYUVOverlay(bmp);

                rect.x = 0;
                rect.y = 0;
                rect.w = pCodecCtx->width;
                rect.h = pCodecCtx->height;

                SDL_DisplayYUVOverlay(bmp, &rect);
                SDL_Delay(10);

            }
        }

        av_free_packet(&packet);
        SDL_PollEvent(&event);

        switch (event.type) {

            case SDL_QUIT:
                SDL_Quit();
                exit(0);
                break;

            default:
                break;
        }

    }


    av_free(pFrame);

    avcodec_close(pCodecCtx);

    avformat_close_input(&pFormatCtx);

    return 0;
}

编译:

gcc -o playvedio playvedio.c -lavutil -lavformat -lavcodec -lavutil -lswscale -lSDL

运行:

./playvedio 文件路径+文件名

 

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 3
博文 32
码字总数 38676
×
曾经花田错
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: