Chinaunix首页 | 论坛 | 博客
  • 博客访问: 85432
  • 博文数量: 14
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 153
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-13 11:26
文章分类

全部博文(14)

文章存档

2015年(2)

2014年(7)

2013年(5)

我的朋友

分类: C/C++

2014-04-22 20:43:01


Tutorial 02: Outputting to the Screen

SDL and Video

To draw to the screen, we're going to use SDL. SDL stands for Simple Direct Layer, and is an excellent library for multimedia, is cross-platform, and is used in several projects. You can get the library at  or you can download the development package for your operating system if there is one. You'll need the libraries to compile the code for this tutorial (and for the rest of them, too).

SDL has many methods for drawing images to the screen, and it has one in particular that is meant for displaying movies on the screen - what it calls a YUV overlay.  is a way of storing raw image data like RGB. Roughly speaking, Y is the brightness (or "luma") component, and U and V are the color components. (It's more complicated than RGB because some of the color information is discarded, and you might have only 1 U and V sample for every 2 Y samples.) SDL's YUV overlay takes in a raw array of YUV data and displays it. It accepts 4 different kinds of YUV formats, but YV12 is the fastest. There is another YUV format called YUV420P that is the same as YV12, except the U and V arrays are switched. The 420 means it is  at a ratio of 4:2:0, basically meaning there is 1 color sample for every 4 luma samples, so the color information is quartered. This is a good way of saving bandwidth, as the human eye does not percieve this change. The "P" in the name means that the format is "planar" — simply meaning that the Y, U, and V components are in separate arrays. ffmpeg can convert images to YUV420P, with the added bonus that many video streams are in that format already, or are easily converted to that format.

So our current plan is to replace the SaveFrame() function from , and instead output our frame to the screen. But first we have to start by seeing how to use the SDL Library. First we have to include the libraries and initalize SDL:

* A note: There is a great deal of annoyance from some people at the convention of calling "YCbCr" "YUV". Generally speaking, YUV is an analog format and YCbCr is a digital format. ffmpeg and SDL both refer to YCbCr as YUV in their code and macros.

#include 
#include 

if((SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
  fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
  exit(1);
}
() essentially tells the library what features we're going to use. SDL_GetError(), of course, is a handy debugging function.


Creating a Display

Now we need a place on the screen to put stuff. The basic area for displaying images with SDL is called a surface:

 *screen;

screen = (pCodecCtx->width, pCodecCtx->height, 0, 0);
if(!screen) {
  fprintf(stderr, "SDL: could not set video mode - exiting\n");
  exit(1);
}
This sets up a screen with the given width and height. The next option is the bit depth of the screen - 0 is a special value that means "same as the current display". (This does not work on OS X; see source.)


Now we create a YUV overlay on that screen so we can input video to it:

 *bmp;

bmp = (pCodecCtx->width, pCodecCtx->height,
                           SDL_YV12_OVERLAY, screen);
As we said before, we are using YV12 to display the image.


Displaying the Image

Well that was simple enough! Now we just need to display the image. Let's go all the way down to where we had our finished frame. We can get rid of all that stuff we had for the RGB frame, and we're going to replace the SaveFrame() with our display code. To display the image, we're going to make an  struct and set its data pointers and linesize to our YUV overlay:

  if(frameFinished) { (bmp);  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];

    // Convert the image into YUV format that SDL uses (&pict, PIX_FMT_YUV420P,
                    ( *)pFrame, pCodecCtx->pix_fmt, 
            pCodecCtx->width, pCodecCtx->height); (bmp);
  }    
First, we lock the overlay because we are going to be writing to it. This is a good habit to get into so you don't have problems later. The struct, as shown before, has a data pointer that is an array of 4 pointers. Since we are dealing with YUV420P here, we only have 3 channels, and therefore only 3 sets of data. Other formats might have a fourth pointer for an alpha channel or something. linesize is what it sounds like. The analogous structures in our YUV overlay are the pixels and pitches variables. ("pitches" is the term SDL uses to refer to the width of a given line of data.) So what we do is point the three arrays of pict.data at our overlay, so when we write to pict, we're actually writing into our overlay, which of course already has the necessary space allocated. Similarly, we get the linesize information directly from our overlay. We change the conversion format to PIX_FMT_YUV420P, and we use  just like before.


Drawing the Image

But we still need to tell SDL to actually show the data we've given it. We also pass this function a rectangle that says where the movie should go and what width and height it should be scaled to. This way, SDL does the scaling for us, and it can be assisted by your graphics processor for faster scaling:

 rect;

  if(frameFinished) {
    /* ... code ... */
    // Convert the image into YUV format that SDL uses (&pict, PIX_FMT_YUV420P,
                    ( *)pFrame, pCodecCtx->pix_fmt, 
            pCodecCtx->width, pCodecCtx->height); (bmp);
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height; (bmp, &rect);
  }
Now our video is displayed!


Let's take this time to show you another feature of SDL: its event system. SDL is set up so that when you type, or move the mouse in the SDL application, or send it a signal, it generates an event. Your program then checks for these events if it wants to handle user input. Your program can also make up events to send the SDL event system. This is especially useful when multithread programming with SDL, which we'll see in . In our program, we're going to poll for events right after we finish processing a packet. For now, we're just going to handle the SDL_QUIT event so we can exit:

 event; (&packet);
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT: ();
      exit(0);
      break;
    default:
      break;
    }
And there we go! Get rid of all the old cruft, and you're ready to compile. If you are using Linux or a variant, the best way to compile using the SDL libs is this:
gcc -o tutorial02 tutorial02.c -lavutil -lavformat -lavcodec -lz -lm \
`sdl-config --cflags --libs`
sdl-config just prints out the proper flags for gcc to include the SDL libraries properly. You may need to do something different to get it to compile on your system; please check the SDL documentation for your system. Once it compiles, go ahead and run it.


What happens when you run this program? The video is going crazy! In fact, we're just displaying all the video frames as fast as we can extract them from the movie file. We don't have any code right now for figuring out when we need to display video. Eventually (in ), we'll get around to syncing the video. But first we're missing something even more important: sound!


源代码如下(修改部分代码,加入一些注释)

点击(此处)折叠或打开

  1. // tutorial02.c
  2. // A pedagogical video player that will stream through every video frame as fast as it can.
  3. //
  4. // This tutorial was written by Stephen Dranger (dranger@gmail.com).
  5. //
  6. // Code based on FFplay, Copyright (c) 2003 Fabrice Bellard,
  7. // and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
  8. // Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
  9. //
  10. // Use the Makefile to build all examples.
  11. //
  12. // Run using
  13. // tutorial02 myvideofile.mpg
  14. //
  15. // to play the video stream on your screen.
  16. //
  17. //
  18. //
  19. //gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lavutil -lswscale -lz -lm -L/opt/libffmpeg/lib/ -I/opt/libffmpeg/include/ -ldl -lpthread -lSDL2 -I/opt/libsdl/include/ -L/opt/libsdl/lib/ -g
  20. //./tutorial02 testvideo.mov
  21. //./tutorial02 rtsp://192.168.17.203/live.sdp

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

  25. #include <SDL2/SDL.h>
  26. #include <SDL2/SDL_thread.h>

  27. #ifdef __MINGW32__
  28. #undef main /* Prevents SDL from overriding main() */
  29. #endif

  30. #include <stdio.h>

  31. int randomInt(int min, int max)
  32. {
  33.     return min + rand() % (max - min + 1);
  34. }

  35. int main(int argc, char *argv[])
  36. {
  37.     AVFormatContext *pFormatCtx = NULL;
  38.     AVCodecContext *pCodecCtx = NULL;
  39.     AVCodec *pCodec = NULL;
  40.     AVFrame *pFrame = NULL;
  41.     AVPacket packet;
  42.     int frameFinished;
  43.     int i, videoStream;
  44.     //float aspect_ratio;

  45.     AVDictionary *optionsDict = NULL;
  46.     struct SwsContext *sws_ctx = NULL;
  47.     //SDL_CreateTexture();
  48.     SDL_Texture *bmp = NULL;
  49.     SDL_Window *screen = NULL;
  50.     SDL_Rect rect;
  51.     SDL_Event event;

  52.     if(argc < 2) {
  53.         fprintf(stderr, "Usage: test \n");
  54.         exit(1);
  55.     }
  56.     
  57.     // Register all formats and codecs
  58.     av_register_all();

  59.     if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
  60.         fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
  61.         exit(1);
  62.     }

  63.     // Open video file
  64.     if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
  65.         return -1; // Couldn't open file

  66.     // Retrieve stream information
  67.     if(avformat_find_stream_info(pFormatCtx, NULL)<0)
  68.         return -1; // Couldn't find stream information

  69.     // Dump information about file onto standard error
  70.     av_dump_format(pFormatCtx, 0, argv[1], 0);

  71.     // Find the first video stream
  72.     videoStream=-1;
  73.     for(i=0; i<pFormatCtx->nb_streams; i++){
  74.         if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
  75.             videoStream=i;
  76.             break;
  77.         }
  78.     }
  79.     
  80.     if(videoStream==-1)
  81.         return -1; // Didn't find a video stream

  82.     // Get a pointer to the codec context for the video stream
  83.     pCodecCtx=pFormatCtx->streams[videoStream]->codec;

  84.     // Find the decoder for the video stream
  85.     pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
  86.     if(pCodec==NULL) {
  87.         fprintf(stderr, "Unsupported codec!\n");
  88.         return -1; // Codec not found
  89.     }

  90.     // Open codec
  91.     if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)
  92.         return -1; // Could not open codec

  93.     // Allocate video frame
  94.     pFrame=avcodec_alloc_frame();

  95.     AVFrame* pFrameYUV = avcodec_alloc_frame();
  96.     if( pFrameYUV == NULL )
  97.         return -1;

  98.     screen = SDL_CreateWindow("Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_FULLSCREEN_DESKTOP);
  99.     SDL_Renderer *renderer = SDL_CreateRenderer(screen, -1, 0);
  100.   
  101.     if(!screen) {
  102.         fprintf(stderr, "SDL: could not set video mode - exiting (%s)\n", SDL_GetError());
  103.         exit(1);
  104.     }

  105.     bmp = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_YV12,SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);
  106.     //SDL_SetTextureBlendMode(bmp,SDL_BLENDMODE_BLEND );

  107.     sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);

  108.     int numBytes = avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
  109.     uint8_t* buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

  110.     avpicture_fill((AVPicture *)pFrameYUV, buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

  111.     // Read frames and save first five frames to disk
  112.     i = 0;
  113.     rect.x = 0;
  114.     rect.y = 0;
  115.     rect.w = pCodecCtx->width;
  116.     rect.h = pCodecCtx->height;
  117.     
  118.     while(av_read_frame(pFormatCtx, &packet) >= 0) {
  119.         // Is this a packet from the video stream?
  120.         if(packet.stream_index==videoStream) {
  121.             // Decode video frame
  122.             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
  123.             // Did we get a video frame?
  124.             if(frameFinished) {
  125.                 sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
  126.                 ////iPitch 计算yuv一行数据占的字节数
  127.                 SDL_UpdateTexture( bmp, &rect, pFrameYUV->data[0], pFrameYUV->linesize[0] );
  128.                 SDL_RenderClear( renderer );
  129.                 SDL_RenderCopy( renderer, bmp, &rect, &rect );
  130.                 SDL_RenderPresent( renderer );
  131.              }
  132.             SDL_Delay(40);    //delay 40ms, fps = 25
  133.         }
  134.     
  135.         // Free the packet that was allocated by av_read_frame
  136.         av_free_packet(&packet);
  137.         SDL_PollEvent(&event);
  138.         switch(event.type) {
  139.             case SDL_QUIT:
  140.                 SDL_Quit();
  141.              exit(0);
  142.              break;
  143.             default:
  144.              break;
  145.         }
  146.     
  147.     }

  148.     SDL_DestroyTexture(bmp);

  149.     // Free the YUV frame
  150.     av_free(pFrame);
  151.     av_free(pFrameYUV);
  152.     // Close the codec
  153.     avcodec_close(pCodecCtx);

  154.     // Close the video file
  155.     avformat_close_input(&pFormatCtx);

  156.     return 0;
  157. }

阅读(1312) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~