반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

buntalk.com

FFmpeg and SDL Tutorial - Playing Sound 본문

FFmpeg

FFmpeg and SDL Tutorial - Playing Sound

분석톡톡 2023. 3. 19. 18:08
반응형

2019/04/08

 

Tutorial 03: Playing Sound
 
 
오디오
 
이제 사운드를 재생한다. SDL은 사운드 출력을 제공한다. SDL_OpenAudio() 함수는 오디오 장치를 열기위한 함수로서 이 함수는 SDL_AudioSpec 구조체를 매개변수로 받는데 여기에 출력하고자하는 오디오에 대한 모든정보를 지정한다.
 
이를 어떻게 설정하는지 보여주기전에 먼저 오디오가 어떻게 처리되는지 알아보자. 디지털 오디오는 샘플의 긴 스트림으로 구성된다. 각 샘플은 오디오 웨이브폼의 값을 나타낸다. 사운드는 특정 샘플 레이트로 녹음되어 있으며 이를 통해 각 샘플을 재생하는데 어느정도 속도를 갖는지를 나타내는 것으로서 초당 샘플의 갯수로 측정된다. 예시 샘플 레이트는 초당 22050 에서 44100가 있는데 라디오와 CD에 각각 사용되는 비율이다. 추가적으로, 대부분의 오디오는 스테레오나 서라운드를 위해 한 채널 이상을 가질 수 있는데 예를 들어, 샘플이 스테레오이면 한번에 2개의 샘플이 나온다. 영화파일에서 데이터를 얻을 때 얼마나 많은 샘플을 얻을지 모르지만 FFmpeg은 특정 샘플을 전달하지 않는데 이 의미는 스테레오 샘플을 자르지 않음을 의미한다.
 
오디오를 재생하기 위해 SDL을 사용하는 방법은 이 것이다. 오디오 옵션을 설정한다. : 샘플 레이트 (SDL구조체에서 frequency를 위한 "freq"라 불린다), 채널의 숫자, 그리고 등등이며 콜백 함수와 사용자 데이터를 설정한다. 오디오를 재생하기 시작하면 SDL은 연속적으로 이 콜백 함수를 호출하고 오디오 버퍼를 채운다. 이 정보를 SDL_AudioSpec 구조체에 할당한다. SDL_OpenAudio() 는 오디오 장치를 열고 다른 AudioSpec 구조체를 건낸다. 이들 스펙들은 우리가 사용할 것이다. 
 
 
오디오 설정하기
 
머릿속에 이들을 잘 기억하자. 아직 오디오 스트림에 대해서는 아무것도 한 게 없다. 비디오 스트림을 얻은 코드와 유사하게 오디오 스트림을 찾을 수 있다.
 
 
// 첫 비디오 스트림 찾기
videoStream = -1;
audioStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; ++i)
{
  if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && videoStream < 0)
  {
    videoStream = i;
  }
  if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioStream < 0)
  {
    audioStream = i;
  }
}
if (videoStream == -1)
{
  return -1; // 비디오 스트림을 찾지 못함
}
if (audioStream == -1)
{
  return -1;
}
 
비디오 스트림에서 그렇듯 AVCodecContext에서 얻고자하는 모든정보를 얻었다.
 
AVCodecContext *aCodecCtxOrig;
AVCodecContext *aCodecCtx;

aCodecCtxOrig = pFormatCtx->streams[audioStream]->codec;​
 

 

만약 이전 튜토리얼과 같이 오디오 코덱을 열어야 한다. 이는 다음과 같다.
 
AVCodec *aCodec;
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if (!aCodec)
{
  fprintf(stderr, "Unsupported codec!\n");
  return -1;
}

// 컨텍스트 복사
aCodecCtx = avcodec_alloc_context3(aCodec);
if (avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0)
{
  fprintf(stderr, "Couldn't copy codec context");
  return -1; // 코덱 컨텍스트 복사 에러
}
/* SDL 오디오를 여기서 초기화 */

avcodec_open2(aCodecCtx, aCodec, NULL);
 
코덱 컨텍스트를 통해 오디오를 설정할 모든 정보를 지정할 수 있다.
 
wanted_spec.freq = aCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;

if (SDL_OpenAudio(&wanted_spec, &spec) < 0)
{
  fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
  return -1;
}
 
이들을 살펴보면
- freq: 이전에 설명한 샘플레이트
- format: SDL에게 어떤 형식을 제공할지를 나타냄. S16SYS의 S는 signed 이며 16은 각 샘플이 16비트이며 SYS는 엔디안순서가 시스템에 의존함을 나타낸다. 이는 avcodec_decode_audio2 가 제공하는 오디오 포맷이다.
- channels: 오디오 채널의 갯수
- silence: 무음을 나타내는 값이다. 오디오가 signed이므로 0이 보통의 값이다. unsigned이면 80.
- samples: 오디오 버퍼의 크기, SDL이 더 많은 오디오를 필요로 하면 재요청한다. 여기에 적절한 값은 512에서 8192 값, ffplay는 1024를 사용한다.
- callback: 실제적 콜백함수를 전달한다. 이 후에 콜백함수에 대해 더 설명할 것이다.
- userdata: SDL이 콜백에게 전달할 void포인터로서 사용자 데이터이다. 코덱 컨텍스트를 지정하는데 이유는 곧 살펴보게 된다.
 
최종적으로, SDL_OpenAudio를 통해 오디오를 연다
 
 
자 이제 스트림으로부터 오디오 정보를 가져왔다. 하지만 이 정보로 어떻게 할 것인가? 영화파일에서 얻은 패킷은 계속적으로 전달될 것이지만 동시에 SDL이 콜백함수를 호출하도록 한다. 해결책은 전역 구조체와 같은 것을 생성해 오디오 패킷을 audio_callback에서 처리할 수 있게 해야한다. 그러므로 패킷 큐를 만들어야 한다. FFmpeg은 이를 위한 구조체를 제공해준다. AVPacketList 는 패킷의 링크드 리스트이다. 여기에 큐 구조체가 있다.
 
typedef struct PacketQueue
{
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;
 
먼저 nb_packets는 size와 다름을 기억하자. size는 packet->size이다. 뮤텍스와 컨디션 변수도 있다. 이는 SDL이 오디오 처리를 분리된 스레드에서 처리하기 때문에 동기화 객체가 필요하다. 큐를 적절하게 락하지 않으면 데이터가 엉망이 될 수 있다. 어떻게 큐를 구현할지 살펴볼 것이다. 어떻게 큐를 만드는지 알고 있겠지만 이를 포함해 SDL함수를 배워보자.
 
먼저 큐를 초기화하는 함수를 만든다.
 
void packet_queue_init(PacketQueue *q)
{
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}

 

 
이제 큐에 데이터를 할당할 함수를 만든다.
 
int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
  AVPacketList *pkt1;
  if (av_dup_packet(pkt) < 0)
  {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
  {
    return -1;
  }
  pkt1->pkt = *pkt;
  pkt1->next = NULL;

  SDL_LockMutex(q->mutex);
  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  SDL_UnlockMutex(q->mutex);
  return 0;
}
 
SDL_LockMutex() 는 큐에서 뮤텍스를 락해 새로운 요소를 추가하고 SDL_CondSignal()을 통해 컨디션 변수로 겟 함수에 시그널을 보낸다. 그러면 Get부분에서 주어진 데이터를 처리할 수 있게 되니 Put하는 이 곳의 뮤텍스는 언락한다.
 
연관된 겟 함수이다. SDL_CondWait() 함수는 함수를 블락 상태로 두게 하는 것이다. (즉, 데이터를 얻을 때 까지 대기)
 
 
int quit = 0;
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;
  SDL_LockMutex(q->mutex);
  for (;;)
  {
    if (quit)
    {
      ret = -1;
      break;
    }
    pkt1 = q->first_pkt;
    if (pkt1)
    {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
        q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    }
    else if (!block)
    {
      ret = 0;
      break;
    }
    else
    {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}
 
함수를 무한 루프에 감쌈으로서 블록하고 데이터를 받아오게 했다. SDL의 SDL_CondWait() 을 사용해 Wait상태에 둔다. 기본적으로 모든 CondWait은 SDL_CondSignal() 로부터 시그널까지 기다린다. 이전에 Put에서 뮤텍스로 가둔것처럼 락을 잡고 풋 함수가 큐에 어떤 것도 못하게 한다. 그러나 SDL_CondWait()을 통해 뮤텍스를 re락하고 다시 전달한다. 다시 시그널을 받을 수 있게 락한다.
 
In Case of Fire
 
전역 quit변수는 프로그램 종료 시그널을 처리하기 위한 것이다. (SDL은 자동적으로 TERM 시그널을 처리한다) 그렇지 않으면 스레드는 영원히 계속될 것이고 kill -9를 해야할 것이다.
 
 
SDL_PollEvent(&event);
switch (event.type)
{
case SDL_QUIT:
  quit = 1;
 
quit플래그에 1을 할당한다.
 
Feeding Packets
 
이제 남은 하나는 큐를 설정하는 것이다.
 
PacketQueue audioq;
main()
{
  ... avcodec_open2(aCodecCtx, aCodec, NULL);
  packet_queue_init(&audioq);
  SDL_PauseAudio(0);
 
SDL_PauseAudio() 는 최종적으로 오디오 장치를 시작한다. 데이터가 없으면 사일런스를 플레이하며 즉시 시작하지 않는다.
 
그러므로, 큐를 설정해야 하며 이제 패킷을 제공하기 시작할 때다. 패킷 리딩 루프로 간다.
 
 
while (av_read_frame(pFormatCtx, &packet) >= 0)
{
  // 이 것이 비디오 스트림에서 얻은 패킷인가?
  if (packet.stream_index == videoStream)
  {
    // 비디오 프레임 디코드
    ...
  }
  else if (packet.stream == audioStream)
  {
    packet_queue_put(&audioq, &packet);
  }
  else
  {
    av_free_packet(&packet);
  }
큐에 전달한 패킷은 해제하지 않는다. 디코드한 후에 해제할 것이다.
 
Fetching Packets
 
이제 audio_callback 함수를 만들어 큐상의 패킷을 페치할 때가 되었다. 콜백은 void callback(void* userdata, Uint8* stream, int len), 형식을 가져야 하며 userdata는 SDL에 전달한 사용자 데이터, stream은 오디오 데이터로 사용할 버퍼, 그리고 len은 버퍼의 크기이다. 
 
 
void audio_callback(void *userdata, Uint8 *stream, int len)
{
  AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  int len1, audio_size;

  static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  static unsigned int audio_buf_size = 0;
  static unsigned int audio_buf_index = 0;
  while (len > 0)
  {
    if (audio_buf_index >= audio_buf_size)
    {
      /* 모든 데이터를 보냈다. 더 받는다. */
      audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
      if (audio_size < 0)
      {
        /* 에러가 있으면 사일런스를 출력 */
        audio_buf_size = 1024;
        memset(audio_buf, 0, audio_buf_size);
      }
      else
      {
        audio_buf_size = audio_size;
      }
      audio_buf_index = 0;
    }
    len1 = audio_buf_size - audio_buf_index;
    if (len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
    len -= len1;
    stream += len1;
    audio_buf_index += len1;
  }
}
 
이 것은 기본적으로 단순한 루프이며 작성할 다른 함수인 audio_decode_frame()으로 부터 데이터를 당겨온다. 즉시 버퍼에 저장하고 stream 에 len 바이트만큼 작성을 시도하고 충분하지않으면 추가적인데이터를 얻거나 나중에 사용하기 위해 저장해둔다. audio_buf 의 크기는 FFmpeg이 제공하는 최대 오디오 프레임 크기의 1.5배로 설정하여 충분하도록 한다.
 
최종적으로 오디오 디코딩
  
디코더의 실제 부분을 살펴보자, audio_decode_frame:
 
 
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size)
{
  static AVPacket pkt;
  static uint8_t *audio_pkt_data = NULL;
  static int audio_pkt_size = 0;
  static AVFrame frame;
  int len1, data_size = 0;
  for (;;)
  {
    while (audio_pkt_size > 0)
    {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
      if (len1 < 0)
      {
        /* 에러이면 프레임 스킵 */
        audio_pkt_size = 0;
        break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1;
      data_size = 0;
      if (got_frame)
      {
        data_size = av_samples_get_buffer_size(NULL, aCodecCtx->channels, frame.nb_samples, aCodecCtx->sample_fmt, 1);
        assert(data_size <= buf_size);
        memcpy(audio_buf, frame.data[0], data_size);
      }
      if (data_size <= 0)
      {
        /* 데이터가 없다. 더 많은 프레임을 얻는다. */
        continue;
      }
      /* 데이터를 가졌다. 이를 반환하고 다시 돌아온다. */
      return data_size;
    }
    if (pkt.data)
      av_free_packet(&pkt);
    if (quit)
    {
      return -1;
    }
    if (packet_queue_get(&audioq, &pkt, 1) < 0)
    {
      return -1;
    }
    audio_pkt_data = pkt.data;
    audio_pkt_size = pkt.size;
  }
}
 
이 전체 처리는 실제로는 함수의 끝 부분인 packet_queue_get() 을 호출하는 곳으로부터 시작한다. 큐에서 패킷을 얻고 그 정보를 저장한다. 그러면, 작업할 패킷을 가지면 avcodec_decode_audio4() 를 호출하며 비슷한 함수인 avcodec_decode_video()와 유사한 처리를 수행한다. 이 상황에서는 패킷이 한 프레임보다 많이 필요할 수 있으므로 패킷의 데이터 전체를 얻으려면 더 자주 호출해야 한다는 점이 다르다. 일단 프레임을 가지면 오디오 버퍼에 복사하여 data_size가 오디오 버퍼보다 더 작게 한다. 또한 audio_buf 로 캐스트했는데 SDL이 8 비트 인트 버퍼를 전달하기 때문이며, FFmpeg은 16비트 버퍼를 제공하기 때문이다. len1 과 data_size.len1 의 차이는 우리가 얼마나 많은 패킷을 사용했느냐이며 data_size는 반환된 로데이터양이다.
 
몇몇 데이터를 얻을 때 큐에서 더 많은 데이터를 얻을 필요가 있는지 또는 처리를 마쳤는지를 확인하기 위해 즉시 반환한다. 만약 처리해야 할 더 많은 패킷이필요하면 나중을 위해 저장해 둔다. 한 패킷을 마치면 패킷을 해제한다.
 
이게 끝이다! 메인 리드 루프로부터 오디오를 받아 큐로 전달했으며 이는 audio_callback 함수에서 읽혀지는데 SDL로 데이터를 전달하는 것이고 SDL은 사운드카드를 통해 사운드를 출력한다.
 
gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
 
자! 비디오는 여전히 빨리 지나가 버리지만, 오디오는 정상적으로 재생된다. 어째서인가? 오디오 정보가 샘플레이트를 가지고 있으며 오디오정보를 최대한 빨리 전달했지만 오디오는 샘플레이트에 따라 재생되기 때문이다.
 
이제 비디오와 오디오 싱킹을 위한 준비를 마쳤다. 하지만 먼저 작은 프로그램을 살펴봐야 한다. 구별된 스레드에서 큐잉업 오디오와 재생하기 위한 처리가 정상작동하기 위한 것이다. 이는 코드를 더욱 모듈러하게 만든다. 비디오를 오디오에 싱킹하기전에 코드를 더 쉽게 해야 한다. 다음은 스레드 생성이다!
 
 
 
 
// tutorial03.c
// A pedagogical video player that will stream through every video frame as fast as it can
// and play audio (out of sync).
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard,
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
//
// gcc -o tutorial03 tutorial03.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed,
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial03 myvideofile.mpg
//
// to play the stream on your screen.

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

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

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

typedef struct PacketQueue
{
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

PacketQueue audioq;

int quit = 0;

void packet_queue_init(PacketQueue *q)
{
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{

  AVPacketList *pkt1;
  if (av_dup_packet(pkt) < 0)
  {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;

  SDL_LockMutex(q->mutex);

  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);

  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;

  SDL_LockMutex(q->mutex);

  for (;;)
  {

    if (quit)
    {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1)
    {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
        q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    }
    else if (!block)
    {
      ret = 0;
      break;
    }
    else
    {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size)
{

  static AVPacket pkt;
  static uint8_t *audio_pkt_data = NULL;
  static int audio_pkt_size = 0;
  static AVFrame frame;

  int len1, data_size = 0;

  for (;;)
  {
    while (audio_pkt_size > 0)
    {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
      if (len1 < 0)
      {
        /* if error, skip frame */
        audio_pkt_size = 0;
        break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1;
      data_size = 0;
      if (got_frame)
      {
        data_size = av_samples_get_buffer_size(NULL,
                                               aCodecCtx->channels,
                                               frame.nb_samples,
                                               aCodecCtx->sample_fmt,
                                               1);
        assert(data_size <= buf_size);
        memcpy(audio_buf, frame.data[0], data_size);
      }
      if (data_size <= 0)
      {
        /* No data yet, get more frames */
        continue;
      }
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if (pkt.data)
      av_free_packet(&pkt);

    if (quit)
    {
      return -1;
    }

    if (packet_queue_get(&audioq, &pkt, 1) < 0)
    {
      return -1;
    }
    audio_pkt_data = pkt.data;
    audio_pkt_size = pkt.size;
  }
}

void audio_callback(void *userdata, Uint8 *stream, int len)
{

  AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  int len1, audio_size;

  static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
  static unsigned int audio_buf_size = 0;
  static unsigned int audio_buf_index = 0;

  while (len > 0)
  {
    if (audio_buf_index >= audio_buf_size)
    {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
      if (audio_size < 0)
      {
        /* If error, output silence */
        audio_buf_size = 1024; // arbitrary?
        memset(audio_buf, 0, audio_buf_size);
      }
      else
      {
        audio_buf_size = audio_size;
      }
      audio_buf_index = 0;
    }
    len1 = audio_buf_size - audio_buf_index;
    if (len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
    len -= len1;
    stream += len1;
    audio_buf_index += len1;
  }
}

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

  AVCodecContext *aCodecCtxOrig = NULL;
  AVCodecContext *aCodecCtx = NULL;
  AVCodec *aCodec = NULL;

  SDL_Overlay *bmp;
  SDL_Surface *screen;
  SDL_Rect rect;
  SDL_Event event;
  SDL_AudioSpec wanted_spec, spec;

  if (argc < 2)
  {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();

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

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

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

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

  // Find the first video stream
  videoStream = -1;
  audioStream = -1;
  for (i = 0; i < pFormatCtx->nb_streams; i++)
  {
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
        videoStream < 0)
    {
      videoStream = i;
    }
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO &&
        audioStream < 0)
    {
      audioStream = i;
    }
  }
  if (videoStream == -1)
    return -1; // Didn't find a video stream
  if (audioStream == -1)
    return -1;

  aCodecCtxOrig = pFormatCtx->streams[audioStream]->codec;
  aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
  if (!aCodec)
  {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  // Copy context
  aCodecCtx = avcodec_alloc_context3(aCodec);
  if (avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0)
  {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Set audio settings from codec info
  wanted_spec.freq = aCodecCtx->sample_rate;
  wanted_spec.format = AUDIO_S16SYS;
  wanted_spec.channels = aCodecCtx->channels;
  wanted_spec.silence = 0;
  wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
  wanted_spec.callback = audio_callback;
  wanted_spec.userdata = aCodecCtx;

  if (SDL_OpenAudio(&wanted_spec, &spec) < 0)
  {
    fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
    return -1;
  }

  avcodec_open2(aCodecCtx, aCodec, NULL);

  // audio_st = pFormatCtx->streams[index]
  packet_queue_init(&audioq);
  SDL_PauseAudio(0);

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

  // Find the decoder for the video stream
  pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if (pCodec == NULL)
  {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0)
  {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    return -1; // Could not open codec

  // Allocate video frame
  pFrame = av_frame_alloc();

  // Make a screen to put our video

#ifndef __DARWIN__
  screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
  screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if (!screen)
  {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
                             pCodecCtx->height,
                             SDL_YV12_OVERLAY,
                             screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
                           pCodecCtx->height,
                           pCodecCtx->pix_fmt,
                           pCodecCtx->width,
                           pCodecCtx->height,
                           PIX_FMT_YUV420P,
                           SWS_BILINEAR,
                           NULL,
                           NULL,
                           NULL);

  // 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_video2(pCodecCtx, pFrame, &frameFinished, &packet);

      // Did we get a video frame?
      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];

        // Convert the image into YUV format that SDL uses
        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);
        av_free_packet(&packet);
      }
    }
    else if (packet.stream_index == audioStream)
    {
      packet_queue_put(&audioq, &packet);
    }
    else
    {
      av_free_packet(&packet);
    }
    // Free the packet that was allocated by av_read_frame
    SDL_PollEvent(&event);
    switch (event.type)
    {
    case SDL_QUIT:
      quit = 1;
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }
  }

  // Free the YUV frame
  av_frame_free(&pFrame);

  // Close the codecs
  avcodec_close(pCodecCtxOrig);
  avcodec_close(pCodecCtx);
  avcodec_close(aCodecCtxOrig);
  avcodec_close(aCodecCtx);

  // Close the video file
  avformat_close_input(&pFormatCtx);

  return 0;
}
반응형