[Часть 2/2] Руководство по FFmpeg и SDL или Как написать видеоплеер менее чем в 1000 строк

Автор оригинала: Dranger
  • Перевод

Выкладываем оставшуюся часть перевода на русский руководства, несколько устаревшего, однако не потерявшего своей актуальности, поскольку этот учебник помогает вникнуть в «кухню» создания видеоприложений с помощью библиотек FFmpeg и SDL.

И хотя мы старались, в таком объёмном тексте неизбежны трудности перевода. Сообщайте о недочётах (желательно, в личных сообщениях) — вместе сделаем лучше.

Оглавление


EDISON Software - web-development
Статья переведена при поддержке компании EDISON.

Среди наших работ много таких, которые связаны со сложной обработкой видео.

Например, мы делали облачный сервис видеонаблюдения, а в другом проекте реализована интеграция систем видеонаблюдения Axxon Next и SureView Immix.

Мы любим и умеем работать с видео! ;-)

Урок 6: Синхронизация аудио


Полный листинг tutorial06.c
// tutorial05.c
// A pedagogical video player that really works!
//
// 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 tutorial05 tutorial05.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
// tutorial04 myvideofile.mpg
//
// to play the video 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>
#include <math.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

#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)

#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0

#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20

#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)

#define VIDEO_PICTURE_QUEUE_SIZE 1

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

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


typedef struct VideoPicture {
  SDL_Overlay *bmp;
  int width, height; /* source height & width */
  int allocated;
  double pts;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

  int             av_sync_type;
  double          external_clock; /* external clock base */
  int64_t         external_clock_time;

  double          audio_clock;
  AVStream        *audio_st;
  AVCodecContext  *audio_ctx;
  PacketQueue     audioq;
  uint8_t         audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  unsigned int    audio_buf_size;
  unsigned int    audio_buf_index;
  AVFrame         audio_frame;
  AVPacket        audio_pkt;
  uint8_t         *audio_pkt_data;
  int             audio_pkt_size;
  int             audio_hw_buf_size;
  double          audio_diff_cum; /* used for AV difference average computation */
  double          audio_diff_avg_coef;
  double          audio_diff_threshold;
  int             audio_diff_avg_count;
  double          frame_timer;
  double          frame_last_pts;
  double          frame_last_delay;
  double          video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
  double          video_current_pts; ///<current displayed pts (different from video_clock if frame fifos are used)
  int64_t         video_current_pts_time;  ///<time (av_gettime) at which we updated video_current_pts - used to have running video pts
  AVStream        *video_st;
  AVCodecContext  *video_ctx;
  PacketQueue     videoq;
  struct SwsContext *sws_ctx;

  VideoPicture    pictq[VIDEO_PICTURE_QUEUE_SIZE];
  int             pictq_size, pictq_rindex, pictq_windex;
  SDL_mutex       *pictq_mutex;
  SDL_cond        *pictq_cond;
  
  SDL_Thread      *parse_tid;
  SDL_Thread      *video_tid;

  char            filename[1024];
  int             quit;
} VideoState;

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
VideoState *global_video_state;

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(global_video_state->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;
}

double get_audio_clock(VideoState *is) {
  double pts;
  int hw_buf_size, bytes_per_sec, n;
  
  pts = is->audio_clock; /* maintained in the audio thread */
  hw_buf_size = is->audio_buf_size - is->audio_buf_index;
  bytes_per_sec = 0;
  n = is->audio_ctx->channels * 2;
  if(is->audio_st) {
    bytes_per_sec = is->audio_ctx->sample_rate * n;
  }
  if(bytes_per_sec) {
    pts -= (double)hw_buf_size / bytes_per_sec;
  }
  return pts;
}
double get_video_clock(VideoState *is) {
  double delta;

  delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
  return is->video_current_pts + delta;
}
double get_external_clock(VideoState *is) {
  return av_gettime() / 1000000.0;
}

double get_master_clock(VideoState *is) {
  if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
    return get_video_clock(is);
  } else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
    return get_audio_clock(is);
  } else {
    return get_external_clock(is);
  }
}


/* Add or subtract samples to get a better sync, return new
   audio buffer size */
int synchronize_audio(VideoState *is, short *samples,
		      int samples_size, double pts) {
  int n;
  double ref_clock;

  n = 2 * is->audio_ctx->channels;
  
  if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
    double diff, avg_diff;
    int wanted_size, min_size, max_size /*, nb_samples */;
    
    ref_clock = get_master_clock(is);
    diff = get_audio_clock(is) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) {
      // accumulate the diffs
      is->audio_diff_cum = diff + is->audio_diff_avg_coef
	* is->audio_diff_cum;
      if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
	is->audio_diff_avg_count++;
      } else {
	avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
	if(fabs(avg_diff) >= is->audio_diff_threshold) {
	  wanted_size = samples_size + ((int)(diff * is->audio_ctx->sample_rate) * n);
	  min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
	  max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
	  if(wanted_size < min_size) {
	    wanted_size = min_size;
	  } else if (wanted_size > max_size) {
	    wanted_size = max_size;
	  }
	  if(wanted_size < samples_size) {
	    /* remove samples */
	    samples_size = wanted_size;
	  } else if(wanted_size > samples_size) {
	    uint8_t *samples_end, *q;
	    int nb;

	    /* add samples by copying final sample*/
	    nb = (samples_size - wanted_size);
	    samples_end = (uint8_t *)samples + samples_size - n;
	    q = samples_end + n;
	    while(nb > 0) {
	      memcpy(q, samples_end, n);
	      q += n;
	      nb -= n;
	    }
	    samples_size = wanted_size;
	  }
	}
      }
    } else {
      /* difference is TOO big; reset diff stuff */
      is->audio_diff_avg_count = 0;
      is->audio_diff_cum = 0;
    }
  }
  return samples_size;
}

int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {

  int len1, data_size = 0;
  AVPacket *pkt = &is->audio_pkt;
  double pts;
  int n;

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

    if(is->quit) {
      return -1;
    }
    /* next packet */
    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    is->audio_pkt_data = pkt->data;
    is->audio_pkt_size = pkt->size;
    /* if update, update the audio clock w/pts */
    if(pkt->pts != AV_NOPTS_VALUE) {
      is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
    }
  }
}

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

  VideoState *is = (VideoState *)userdata;
  int len1, audio_size;
  double pts;

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

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
  SDL_Event event;
  event.type = FF_REFRESH_EVENT;
  event.user.data1 = opaque;
  SDL_PushEvent(&event);
  return 0; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
  SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

void video_display(VideoState *is) {

  SDL_Rect rect;
  VideoPicture *vp;
  float aspect_ratio;
  int w, h, x, y;
  int i;

  vp = &is->pictq[is->pictq_rindex];
  if(vp->bmp) {
    if(is->video_ctx->sample_aspect_ratio.num == 0) {
      aspect_ratio = 0;
    } else {
      aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
	is->video_ctx->width / is->video_ctx->height;
    }
    if(aspect_ratio <= 0.0) {
      aspect_ratio = (float)is->video_ctx->width /
	(float)is->video_ctx->height;
    }
    h = screen->h;
    w = ((int)rint(h * aspect_ratio)) & -3;
    if(w > screen->w) {
      w = screen->w;
      h = ((int)rint(w / aspect_ratio)) & -3;
    }
    x = (screen->w - w) / 2;
    y = (screen->h - h) / 2;
    
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;
    SDL_LockMutex(screen_mutex);
    SDL_DisplayYUVOverlay(vp->bmp, &rect);
    SDL_UnlockMutex(screen_mutex);
  }
}

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  double actual_delay, delay, sync_threshold, ref_clock, diff;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];
      
      is->video_current_pts = vp->pts;
      is->video_current_pts_time = av_gettime();
      delay = vp->pts - is->frame_last_pts; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;



      /* update delay to sync to audio if not master source */
      if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
	ref_clock = get_master_clock(is);
	diff = vp->pts - ref_clock;
	
	/* Skip or repeat the frame. Take delay into account
	   FFPlay still doesn't "know if this is the best guess." */
	sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
	if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
	  if(diff <= -sync_threshold) {
	    delay = 0;
	  } else if(diff >= sync_threshold) {
	    delay = 2 * delay;
	  }
	}
      }
      is->frame_timer += delay;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}
      
void alloc_picture(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;

  vp = &is->pictq[is->pictq_windex];
  if(vp->bmp) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  SDL_LockMutex(screen_mutex);
  vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
				 is->video_ctx->height,
				 SDL_YV12_OVERLAY,
				 screen);
  SDL_UnlockMutex(screen_mutex);

  vp->width = is->video_ctx->width;
  vp->height = is->video_ctx->height;
  vp->allocated = 1;

}

int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

  /* wait until we have space for a new pic */
  SDL_LockMutex(is->pictq_mutex);
  while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
	!is->quit) {
    SDL_CondWait(is->pictq_cond, is->pictq_mutex);
  }
  SDL_UnlockMutex(is->pictq_mutex);

  if(is->quit)
    return -1;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  if(!vp->bmp ||
     vp->width != is->video_ctx->width ||
     vp->height != is->video_ctx->height) {
    SDL_Event event;

    vp->allocated = 0;
    alloc_picture(is);
    if(is->quit) {
      return -1;
    }
  }

  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    vp->pts = pts;
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    pict.data[0] = vp->bmp->pixels[0];
    pict.data[1] = vp->bmp->pixels[2];
    pict.data[2] = vp->bmp->pixels[1];
    
    pict.linesize[0] = vp->bmp->pitches[0];
    pict.linesize[1] = vp->bmp->pitches[2];
    pict.linesize[2] = vp->bmp->pitches[1];
    
    // Convert the image into YUV format that SDL uses
    sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, is->video_ctx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(vp->bmp);
    /* now we inform our display thread that we have a pic ready */
    if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
      is->pictq_windex = 0;
    }
    SDL_LockMutex(is->pictq_mutex);
    is->pictq_size++;
    SDL_UnlockMutex(is->pictq_mutex);
  }
  return 0;
}

double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {

  double frame_delay;

  if(pts != 0) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_ctx->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
  is->video_clock += frame_delay;
  return pts;
}

int video_thread(void *arg) {
  VideoState *is = (VideoState *)arg;
  AVPacket pkt1, *packet = &pkt1;
  int frameFinished;
  AVFrame *pFrame;
  double pts;

  pFrame = av_frame_alloc();

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;

    // Decode video frame
    avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);

    if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
    } else {
      pts = 0;
    }
    pts *= av_q2d(is->video_st->time_base);

    // Did we get a video frame?
    if(frameFinished) {
      pts = synchronize_video(is, pFrame, pts);
      if(queue_picture(is, pFrame, pts) < 0) {
	break;
      }
    }
    av_free_packet(packet);
  }
  av_frame_free(&pFrame);
  return 0;
}

int stream_component_open(VideoState *is, int stream_index) {

  AVFormatContext *pFormatCtx = is->pFormatCtx;
  AVCodecContext *codecCtx = NULL;
  AVCodec *codec = NULL;
  SDL_AudioSpec wanted_spec, spec;

  if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
    return -1;
  }

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

  codecCtx = avcodec_alloc_context3(codec);
  if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    wanted_spec.freq = codecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = codecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = is;
    
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
      fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
      return -1;
    }
    is->audio_hw_buf_size = spec.size;
  }
  if(avcodec_open2(codecCtx, codec, NULL) < 0) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  switch(codecCtx->codec_type) {
  case AVMEDIA_TYPE_AUDIO:
    is->audioStream = stream_index;
    is->audio_st = pFormatCtx->streams[stream_index];
    is->audio_ctx = codecCtx;
    is->audio_buf_size = 0;
    is->audio_buf_index = 0;
    memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
    packet_queue_init(&is->audioq);
    SDL_PauseAudio(0);
    break;
  case AVMEDIA_TYPE_VIDEO:
    is->videoStream = stream_index;
    is->video_st = pFormatCtx->streams[stream_index];
    is->video_ctx = codecCtx;

    is->frame_timer = (double)av_gettime() / 1000000.0;
    is->frame_last_delay = 40e-3;
    is->video_current_pts_time = av_gettime();

    packet_queue_init(&is->videoq);
    is->video_tid = SDL_CreateThread(video_thread, is);
    is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
				 is->video_ctx->pix_fmt, is->video_ctx->width,
				 is->video_ctx->height, PIX_FMT_YUV420P,
				 SWS_BILINEAR, NULL, NULL, NULL
				 );
    break;
  default:
    break;
  }
}

int decode_thread(void *arg) {

  VideoState *is = (VideoState *)arg;
  AVFormatContext *pFormatCtx;
  AVPacket pkt1, *packet = &pkt1;

  int video_index = -1;
  int audio_index = -1;
  int i;

  is->videoStream=-1;
  is->audioStream=-1;

  global_video_state = is;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // 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, is->filename, 0);
  
  // Find the first video stream

  for(i=0; i<pFormatCtx->nb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       video_index < 0) {
      video_index=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audio_index < 0) {
      audio_index=i;
    }
  }
  if(audio_index >= 0) {
    stream_component_open(is, audio_index);
  }
  if(video_index >= 0) {
    stream_component_open(is, video_index);
  }   

  if(is->videoStream < 0 || is->audioStream < 0) {
    fprintf(stderr, "%s: could not open codecs\n", is->filename);
    goto fail;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    if(is->audioq.size > MAX_AUDIOQ_SIZE ||
       is->videoq.size > MAX_VIDEOQ_SIZE) {
      SDL_Delay(10);
      continue;
    }
    if(av_read_frame(is->pFormatCtx, packet) < 0) {
      if(is->pFormatCtx->pb->error == 0) {
	SDL_Delay(100); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    if(packet->stream_index == is->videoStream) {
      packet_queue_put(&is->videoq, packet);
    } else if(packet->stream_index == is->audioStream) {
      packet_queue_put(&is->audioq, packet);
    } else {
      av_free_packet(packet);
    }
  }
  /* all done - wait for it */
  while(!is->quit) {
    SDL_Delay(100);
  }

 fail:
  if(1){
    SDL_Event event;
    event.type = FF_QUIT_EVENT;
    event.user.data1 = is;
    SDL_PushEvent(&event);
  }
  return 0;
}

int main(int argc, char *argv[]) {

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  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);
  }

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
        screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  screen_mutex = SDL_CreateMutex();

  av_strlcpy(is->filename, argv[1], sizeof(is->filename));

  is->pictq_mutex = SDL_CreateMutex();
  is->pictq_cond = SDL_CreateCond();

  schedule_refresh(is, 40);

  is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
  is->parse_tid = SDL_CreateThread(decode_thread, is);
  if(!is->parse_tid) {
    av_free(is);
    return -1;
  }
  for(;;) {

    SDL_WaitEvent(&event);
    switch(event.type) {
    case FF_QUIT_EVENT:
    case SDL_QUIT:
      is->quit = 1;
      SDL_Quit();
      return 0;
      break;
    case FF_REFRESH_EVENT:
      video_refresh_timer(event.user.data1);
      break;
    default:
      break;
    }
  }
  return 0;
}

Синхронизация аудио


Теперь, когда у нас есть более-менее пристойный плеер, на котором даже можно поглядеть кино, давайте сведём теперь концы с концами. В прошлый раз мы слегка коснулись синхронизации, а именно — синхронизация звука с видео, именно в таком порядке, не наоборот. Мы собираемся сделать это таким же макаром, как и с видео: сделать внутренние видеочасы, чтобы отслеживать, как далеко находится видеопоток, и синхронизировать с ним аудио. Позже мы ещё более обобщим — синхронизируем аудио и видео с внешними часами.

Реализация видеочасов


Теперь мы хотим сделать видеочасы, аналогичные аудиочасам, которые были у нас в прошлый раз: внутреннее значение, которое возвращает текущее смещение времени воспроизводимого в данный момент видео. Можно подумать, что это будет так же просто, как обновить таймер текущим PTS последнего отображаемого кадра. Однако не забывайте, что время между видеокадрами может быть слишком большим, если мы опускаемся до миллисекундного уровня. Поэтому решение заключается в том, чтобы отслеживать другое значение, время, в которое мы устанавливаем видеочасы на PTS последнего кадра. Таким образом, текущее значение видеочасов будет PTS_of_last_frame + (current_timetime_elapsed_since_PTS_value_was_set). Это решение очень похоже на то, что мы сделали с get_audio_clock.

Итак, в нашу полномасштабную структуру мы собираемся поместить double video_current_pts и int64_t video_current_pts_time. Обновление часов будет происходить в функции video_refresh_timer:

void video_refresh_timer(void *userdata) {

  /* ... */

  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];

      is->video_current_pts = vp->pts;
      is->video_current_pts_time = av_gettime();

Не забываем инициализировать его в stream_component_open:

is->video_current_pts_time = av_gettime();

И теперь все, что нам нужно, это каким-то способом получить информацию:

double get_video_clock(VideoState *is) {
  double delta;

  delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
  return is->video_current_pts + delta;
}

Абстрагируясь от часов


Но зачем заставлять себя использовать видеочасы? Можно пойти дальше и изменить наш код синхронизации видео, чтобы аудио и видео не пытались синхронизировать друг друга. Представьте себе какой будет бардак, если мы попытаемся сделать это опцией командной строки, как в FFplay. Итак, давайте абстрагируемся: мы создадим новую функцию-обертку, get_master_clock, которая проверяет переменную av_sync_type, а затем вызывает get_audio_clock, get_video_clock или вообще любые другие часы, которые могли бы использовать. Мы можем даже использовать часы компьютера, которые назовем get_external_clock:

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

double get_master_clock(VideoState *is) {
  if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
    return get_video_clock(is);
  } else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
    return get_audio_clock(is);
  } else {
    return get_external_clock(is);
  }
}
main() {
...
  is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
...
}

Синхронизация аудио


Теперь самое сложное: синхронизировать аудио с видеочасами. Наша стратегия состоит в том, чтобы измерить, где находится аудио, сравнить его с видеочасами, а затем выяснить, сколько сэмплов нам нужно отрегулировать, то есть нужно ли нам ускоряться путем отбрасывания сэмплов или замедлиться, добавляя?

Мы запускаем функцию synchronize_audio каждый раз, когда обрабатываем каждый набор аудиосэмплов, который получим, чтобы правильно уменьшить или увеличить этот набор. Однако мы не хотим синхронизировать всё время, ибо обработка аудио происходит намного чаще, чем обработка видеопакетов. Итак, мы собираемся установить минимальное количество последовательных вызовов функции synchronize_audio, которые считаются несинхронизироваными, прежде чем удосужимся что-либо сделать. Конечно, как и в прошлый раз, «несинхронизация» означает, что аудиочасы и видеочасы отличаются на величину, большую чем порог синхронизации.

Так что мы собираемся использовать дробный коэффициент, скажем, с, и теперь, допустим, мы получили N наборов аудиосэмплов, которые были не синхронизированы. Количество сэмплов, которое мы не синхронизируем, также может сильно варьироваться, поэтому мы возьмём среднее значение того, насколько сильно не синхронизирован каждый из них. Например, первый вызов мог показать, что мы не синхронизированы на 40 мс, следующий на 50 мс и так далее. Но мы не собираемся брать простое среднее, потому что самые последние значения более важны, чем те, что идут перед ними. Итак, мы собираемся использовать дробный коэффициент, скажем, c, и суммировать различия следующим образом: diff_sum = new_diff + diff_sum * c. Когда мы готовы найти среднюю разницу, мы просто вычисляем avg_diff = diff_sum * (1 − c).

Что здесь, чёрт возьми, происходит? Уравнение выглядит как какая-то магия. Ну, это в основном взвешенное среднее с использованием геометрического ряда в качестве весов. Я не знаю, есть ли название для этого (я даже проверял в Википедии!), Но для получения дополнительной информации, вот объяснение (или вот: weightedmean.txt).

Вот как выглядит наша функция:

/* Add or subtract samples to get a better sync, return new
   audio buffer size */
int synchronize_audio(VideoState *is, short *samples,
		      int samples_size, double pts) {
  int n;
  double ref_clock;
  
  n = 2 * is->audio_st->codec->channels;
  
  if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
    double diff, avg_diff;
    int wanted_size, min_size, max_size, nb_samples;
    
    ref_clock = get_master_clock(is);
    diff = get_audio_clock(is) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) {
      // accumulate the diffs
      is->audio_diff_cum = diff + is->audio_diff_avg_coef
	* is->audio_diff_cum;
      if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
	is->audio_diff_avg_count++;
      } else {
	avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);

       /* Shrinking/expanding buffer code.... */

      }
    } else {
      /* difference is TOO big; reset diff stuff */
      is->audio_diff_avg_count = 0;
      is->audio_diff_cum = 0;
    }
  }
  return samples_size;
}

Так что у нас все хорошо; мы приблизительно знаем, насколько звук не согласован с видео или с тем, что мы используем в качестве часов. Итак, давайте теперь посчитаем, сколько сэмплов нам нужно добавить или отбросить, поместив этот код в раздел «Сокращающий/расширяющий буферный код»:

if(fabs(avg_diff) >= is->audio_diff_threshold) {
  wanted_size = samples_size + 
  ((int)(diff * is->audio_st->codec->sample_rate) * n);
  min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX)
                             / 100);
  max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) 
                             / 100);
  if(wanted_size < min_size) {
    wanted_size = min_size;
  } else if (wanted_size > max_size) {
    wanted_size = max_size;
  }

Помните, что audio_length * (sample_rate * # of channel * 2) — это количество сэмплов в audio_length секундах аудио. Следовательно, количество сэмплов, которое мы хотим, будет равным количеству сэмплов, которые у нас уже есть, плюс или минус количество сэмплов, которые соответствуют количеству времени, в течение которого воспроизводился звук. Мы также установим предел того, насколько большой или маленькой может быть наша коррекция, потому что если мы слишком сильно изменим наш буфер, это будет слишком раздражающим для пользователя.

Исправление количества сэмплов


Теперь нам нужно исправить звук. Возможно, вы заметили, что наша функция synchronize_audio возвращает размер сэмпла, который затем сообщит нам, сколько байтов нужно отправить потоку. Так что нам просто нужно настроить размер сэмпла на требуемое значение. Это работает для уменьшения размера сэмпла. Но если надо увеличить его, мы не можем просто увеличить размер сэмпла, потому что в буфере больше нет данных! Поэтому мы должны немного добавить. Но что именно добавить? Было бы глупо пытаться экстраполировать аудио, поэтому давайте просто используем аудио, которое у нас уже есть, добавив в буфер значение последнего сэмпла.

if(wanted_size < samples_size) {
  /* remove samples */
  samples_size = wanted_size;
} else if(wanted_size > samples_size) {
  uint8_t *samples_end, *q;
  int nb;

  /* add samples by copying final samples */
  nb = (samples_size - wanted_size);
  samples_end = (uint8_t *)samples + samples_size - n;
  q = samples_end + n;
  while(nb > 0) {
    memcpy(q, samples_end, n);
    q += n;
    nb -= n;
  }
  samples_size = wanted_size;
}

Теперь мы возвращаем размер сэмпла, и мы закончили с этой функцией. Всё, что нам нужно сделать сейчас, это использовать вот это:

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

  VideoState *is = (VideoState *)userdata;
  int len1, audio_size;
  double pts;

  while(len > 0) {
    if(is->audio_buf_index >= is->audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
      if(audio_size < 0) {
	/* If error, output silence */
	is->audio_buf_size = 1024;
	memset(is->audio_buf, 0, is->audio_buf_size);
      } else {
	audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,
				       audio_size, pts);
	is->audio_buf_size = audio_size;

Все, что мы сделали, это вставили вызов synchronize_audio. (Также обязательно проверьте исходный код, в котором мы инициализируем переменные, которые я не удосужился определить.)

И последнее, прежде чем закончим: нам нужно добавить условие «если», чтобы убедиться, что мы не синхронизируем видео, если это главные часы:

if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
  ref_clock = get_master_clock(is);
  diff = vp->pts - ref_clock;

  /* Skip or repeat the frame. Take delay into account
     FFPlay still doesn't "know if this is the best guess." */
  sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay :
                    AV_SYNC_THRESHOLD;
  if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
    if(diff <= -sync_threshold) {
      delay = 0;
    } else if(diff >= sync_threshold) {
      delay = 2 * delay;
    }
  }
}

И это работает! Убедитесь, что вы проверили исходный файл, чтобы инициализировать любые переменные, которые я не удосужился определить или инициализировать. Затем скомпилируйте:

gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

и полёт будет нормальный.

В последнем уроке сделаем перемотку.






Урок 7: Поиск


Полный листинг tutorial07.c
// tutorial05.c
// A pedagogical video player that really works!
//
// 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 tutorial05 tutorial05.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
// tutorial04 myvideofile.mpg
//
// to play the video 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>
#include <math.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

#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)

#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0

#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20

#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)

#define VIDEO_PICTURE_QUEUE_SIZE 1

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

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


typedef struct VideoPicture {
  SDL_Overlay *bmp;
  int width, height; /* source height & width */
  int allocated;
  double pts;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

  int             av_sync_type;
  double          external_clock; /* external clock base */
  int64_t         external_clock_time;
  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

  double          audio_clock;
  AVStream        *audio_st;
  AVCodecContext  *audio_ctx;
  PacketQueue     audioq;
  uint8_t         audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  unsigned int    audio_buf_size;
  unsigned int    audio_buf_index;
  AVFrame         audio_frame;
  AVPacket        audio_pkt;
  uint8_t         *audio_pkt_data;
  int             audio_pkt_size;
  int             audio_hw_buf_size;
  double          audio_diff_cum; /* used for AV difference average computation */
  double          audio_diff_avg_coef;
  double          audio_diff_threshold;
  int             audio_diff_avg_count;
  double          frame_timer;
  double          frame_last_pts;
  double          frame_last_delay;
  double          video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
  double          video_current_pts; ///<current displayed pts (different from video_clock if frame fifos are used)
  int64_t         video_current_pts_time;  ///<time (av_gettime) at which we updated video_current_pts - used to have running video pts
  AVStream        *video_st;
  AVCodecContext  *video_ctx;
  PacketQueue     videoq;
  struct SwsContext *sws_ctx;

  VideoPicture    pictq[VIDEO_PICTURE_QUEUE_SIZE];
  int             pictq_size, pictq_rindex, pictq_windex;
  SDL_mutex       *pictq_mutex;
  SDL_cond        *pictq_cond;
  
  SDL_Thread      *parse_tid;
  SDL_Thread      *video_tid;

  char            filename[1024];
  int             quit;
} VideoState;

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
VideoState *global_video_state;
AVPacket flush_pkt;

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(pkt != &flush_pkt && 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(global_video_state->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;
}

static void packet_queue_flush(PacketQueue *q) {
  AVPacketList *pkt, *pkt1;

  SDL_LockMutex(q->mutex);
  for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
    pkt1 = pkt->next;
    av_free_packet(&pkt->pkt);
    av_freep(&pkt);
  }
  q->last_pkt = NULL;
  q->first_pkt = NULL;
  q->nb_packets = 0;
  q->size = 0;
  SDL_UnlockMutex(q->mutex);
}

double get_audio_clock(VideoState *is) {
  double pts;
  int hw_buf_size, bytes_per_sec, n;
  
  pts = is->audio_clock; /* maintained in the audio thread */
  hw_buf_size = is->audio_buf_size - is->audio_buf_index;
  bytes_per_sec = 0;
  n = is->audio_ctx->channels * 2;
  if(is->audio_st) {
    bytes_per_sec = is->audio_ctx->sample_rate * n;
  }
  if(bytes_per_sec) {
    pts -= (double)hw_buf_size / bytes_per_sec;
  }
  return pts;
}
double get_video_clock(VideoState *is) {
  double delta;

  delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
  return is->video_current_pts + delta;
}
double get_external_clock(VideoState *is) {
  return av_gettime() / 1000000.0;
}

double get_master_clock(VideoState *is) {
  if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
    return get_video_clock(is);
  } else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
    return get_audio_clock(is);
  } else {
    return get_external_clock(is);
  }
}


/* Add or subtract samples to get a better sync, return new
   audio buffer size */
int synchronize_audio(VideoState *is, short *samples,
		      int samples_size, double pts) {
  int n;
  double ref_clock;

  n = 2 * is->audio_ctx->channels;
  
  if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
    double diff, avg_diff;
    int wanted_size, min_size, max_size /*, nb_samples */;
    
    ref_clock = get_master_clock(is);
    diff = get_audio_clock(is) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) {
      // accumulate the diffs
      is->audio_diff_cum = diff + is->audio_diff_avg_coef
	* is->audio_diff_cum;
      if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
	is->audio_diff_avg_count++;
      } else {
	avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
	if(fabs(avg_diff) >= is->audio_diff_threshold) {
	  wanted_size = samples_size + ((int)(diff * is->audio_ctx->sample_rate) * n);
	  min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
	  max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
	  if(wanted_size < min_size) {
	    wanted_size = min_size;
	  } else if (wanted_size > max_size) {
	    wanted_size = max_size;
	  }
	  if(wanted_size < samples_size) {
	    /* remove samples */
	    samples_size = wanted_size;
	  } else if(wanted_size > samples_size) {
	    uint8_t *samples_end, *q;
	    int nb;

	    /* add samples by copying final sample*/
	    nb = (samples_size - wanted_size);
	    samples_end = (uint8_t *)samples + samples_size - n;
	    q = samples_end + n;
	    while(nb > 0) {
	      memcpy(q, samples_end, n);
	      q += n;
	      nb -= n;
	    }
	    samples_size = wanted_size;
	  }
	}
      }
    } else {
      /* difference is TOO big; reset diff stuff */
      is->audio_diff_avg_count = 0;
      is->audio_diff_cum = 0;
    }
  }
  return samples_size;
}

int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {

  int len1, data_size = 0;
  AVPacket *pkt = &is->audio_pkt;
  double pts;
  int n;

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

    if(is->quit) {
      return -1;
    }
    /* next packet */
    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    if(pkt->data == flush_pkt.data) {
      avcodec_flush_buffers(is->audio_ctx);
      continue;
    }
    is->audio_pkt_data = pkt->data;
    is->audio_pkt_size = pkt->size;
    /* if update, update the audio clock w/pts */
    if(pkt->pts != AV_NOPTS_VALUE) {
      is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
    }
  }
}

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

  VideoState *is = (VideoState *)userdata;
  int len1, audio_size;
  double pts;

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

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
  SDL_Event event;
  event.type = FF_REFRESH_EVENT;
  event.user.data1 = opaque;
  SDL_PushEvent(&event);
  return 0; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
  SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

void video_display(VideoState *is) {

  SDL_Rect rect;
  VideoPicture *vp;
  float aspect_ratio;
  int w, h, x, y;
  int i;

  vp = &is->pictq[is->pictq_rindex];
  if(vp->bmp) {
    if(is->video_ctx->sample_aspect_ratio.num == 0) {
      aspect_ratio = 0;
    } else {
      aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
	is->video_ctx->width / is->video_ctx->height;
    }
    if(aspect_ratio <= 0.0) {
      aspect_ratio = (float)is->video_ctx->width /
	(float)is->video_ctx->height;
    }
    h = screen->h;
    w = ((int)rint(h * aspect_ratio)) & -3;
    if(w > screen->w) {
      w = screen->w;
      h = ((int)rint(w / aspect_ratio)) & -3;
    }
    x = (screen->w - w) / 2;
    y = (screen->h - h) / 2;
    
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;
    SDL_LockMutex(screen_mutex);
    SDL_DisplayYUVOverlay(vp->bmp, &rect);
    SDL_UnlockMutex(screen_mutex);
  }
}

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  double actual_delay, delay, sync_threshold, ref_clock, diff;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];
      
      is->video_current_pts = vp->pts;
      is->video_current_pts_time = av_gettime();
      delay = vp->pts - is->frame_last_pts; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;



      /* update delay to sync to audio if not master source */
      if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
	ref_clock = get_master_clock(is);
	diff = vp->pts - ref_clock;
	
	/* Skip or repeat the frame. Take delay into account
	   FFPlay still doesn't "know if this is the best guess." */
	sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
	if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
	  if(diff <= -sync_threshold) {
	    delay = 0;
	  } else if(diff >= sync_threshold) {
	    delay = 2 * delay;
	  }
	}
      }
      is->frame_timer += delay;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}
      
void alloc_picture(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;

  vp = &is->pictq[is->pictq_windex];
  if(vp->bmp) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  SDL_LockMutex(screen_mutex);
  vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
				 is->video_ctx->height,
				 SDL_YV12_OVERLAY,
				 screen);
  SDL_UnlockMutex(screen_mutex);

  vp->width = is->video_ctx->width;
  vp->height = is->video_ctx->height;
  vp->allocated = 1;

}

int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

  /* wait until we have space for a new pic */
  SDL_LockMutex(is->pictq_mutex);
  while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
	!is->quit) {
    SDL_CondWait(is->pictq_cond, is->pictq_mutex);
  }
  SDL_UnlockMutex(is->pictq_mutex);

  if(is->quit)
    return -1;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  if(!vp->bmp ||
     vp->width != is->video_ctx->width ||
     vp->height != is->video_ctx->height) {
    SDL_Event event;

    vp->allocated = 0;
    alloc_picture(is);
    if(is->quit) {
      return -1;
    }
  }
  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    vp->pts = pts;
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    pict.data[0] = vp->bmp->pixels[0];
    pict.data[1] = vp->bmp->pixels[2];
    pict.data[2] = vp->bmp->pixels[1];
    
    pict.linesize[0] = vp->bmp->pitches[0];
    pict.linesize[1] = vp->bmp->pitches[2];
    pict.linesize[2] = vp->bmp->pitches[1];
    
    // Convert the image into YUV format that SDL uses
    sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, is->video_ctx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(vp->bmp);
    /* now we inform our display thread that we have a pic ready */
    if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
      is->pictq_windex = 0;
    }
    SDL_LockMutex(is->pictq_mutex);
    is->pictq_size++;
    SDL_UnlockMutex(is->pictq_mutex);
  }
  return 0;
}

double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {

  double frame_delay;

  if(pts != 0) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_ctx->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
  is->video_clock += frame_delay;
  return pts;
}

int video_thread(void *arg) {
  VideoState *is = (VideoState *)arg;
  AVPacket pkt1, *packet = &pkt1;
  int frameFinished;
  AVFrame *pFrame;
  double pts;

  pFrame = av_frame_alloc();

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;

    // Decode video frame
    avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);

    if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
      pts = av_frame_get_best_effort_timestamp(pFrame);
    } else {
      pts = 0;
    }
    pts *= av_q2d(is->video_st->time_base);

    // Did we get a video frame?
    if(frameFinished) {
      pts = synchronize_video(is, pFrame, pts);
      if(queue_picture(is, pFrame, pts) < 0) {
	break;
      }
    }
    av_free_packet(packet);
  }
  av_frame_free(&pFrame);
  return 0;
}

int stream_component_open(VideoState *is, int stream_index) {

  AVFormatContext *pFormatCtx = is->pFormatCtx;
  AVCodecContext *codecCtx = NULL;
  AVCodec *codec = NULL;
  SDL_AudioSpec wanted_spec, spec;

  if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
    return -1;
  }

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

  codecCtx = avcodec_alloc_context3(codec);
  if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    wanted_spec.freq = codecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = codecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = is;
    
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
      fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
      return -1;
    }
    is->audio_hw_buf_size = spec.size;
  }
  if(avcodec_open2(codecCtx, codec, NULL) < 0) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  switch(codecCtx->codec_type) {
  case AVMEDIA_TYPE_AUDIO:
    is->audioStream = stream_index;
    is->audio_st = pFormatCtx->streams[stream_index];
    is->audio_ctx = codecCtx;
    is->audio_buf_size = 0;
    is->audio_buf_index = 0;
    memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
    packet_queue_init(&is->audioq);
    SDL_PauseAudio(0);
    break;
  case AVMEDIA_TYPE_VIDEO:
    is->videoStream = stream_index;
    is->video_st = pFormatCtx->streams[stream_index];
    is->video_ctx = codecCtx;

    is->frame_timer = (double)av_gettime() / 1000000.0;
    is->frame_last_delay = 40e-3;
    is->video_current_pts_time = av_gettime();

    packet_queue_init(&is->videoq);
    is->video_tid = SDL_CreateThread(video_thread, is);
    is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
				 is->video_ctx->pix_fmt, is->video_ctx->width,
				 is->video_ctx->height, PIX_FMT_YUV420P,
				 SWS_BILINEAR, NULL, NULL, NULL
				 );
    break;
  default:
    break;
  }
}

int decode_thread(void *arg) {

  VideoState *is = (VideoState *)arg;
  AVFormatContext *pFormatCtx;
  AVPacket pkt1, *packet = &pkt1;

  int video_index = -1;
  int audio_index = -1;
  int i;

  is->videoStream=-1;
  is->audioStream=-1;

  global_video_state = is;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // 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, is->filename, 0);
  
  // Find the first video stream

  for(i=0; i<pFormatCtx->nb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       video_index < 0) {
      video_index=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audio_index < 0) {
      audio_index=i;
    }
  }
  if(audio_index >= 0) {
    stream_component_open(is, audio_index);
  }
  if(video_index >= 0) {
    stream_component_open(is, video_index);
  }   

  if(is->videoStream < 0 || is->audioStream < 0) {
    fprintf(stderr, "%s: could not open codecs\n", is->filename);
    goto fail;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    if(is->seek_req) {
      int stream_index= -1;
      int64_t seek_target = is->seek_pos;

      if     (is->videoStream >= 0) stream_index = is->videoStream;
      else if(is->audioStream >= 0) stream_index = is->audioStream;

      if(stream_index>=0){
	seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
				  pFormatCtx->streams[stream_index]->time_base);
      }
      if(av_seek_frame(is->pFormatCtx, stream_index, 
		       seek_target, is->seek_flags) < 0) {
	fprintf(stderr, "%s: error while seeking\n",
		is->pFormatCtx->filename);
      } else {

	if(is->audioStream >= 0) {
	  packet_queue_flush(&is->audioq);
	  packet_queue_put(&is->audioq, &flush_pkt);
	}
	if(is->videoStream >= 0) {
	  packet_queue_flush(&is->videoq);
	  packet_queue_put(&is->videoq, &flush_pkt);
	}
      }
      is->seek_req = 0;
    }

    if(is->audioq.size > MAX_AUDIOQ_SIZE ||
       is->videoq.size > MAX_VIDEOQ_SIZE) {
      SDL_Delay(10);
      continue;
    }
    if(av_read_frame(is->pFormatCtx, packet) < 0) {
      if(is->pFormatCtx->pb->error == 0) {
	SDL_Delay(100); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    if(packet->stream_index == is->videoStream) {
      packet_queue_put(&is->videoq, packet);
    } else if(packet->stream_index == is->audioStream) {
      packet_queue_put(&is->audioq, packet);
    } else {
      av_free_packet(packet);
    }
  }
  /* all done - wait for it */
  while(!is->quit) {
    SDL_Delay(100);
  }

 fail:
  if(1){
    SDL_Event event;
    event.type = FF_QUIT_EVENT;
    event.user.data1 = is;
    SDL_PushEvent(&event);
  }
  return 0;
}

void stream_seek(VideoState *is, int64_t pos, int rel) {

  if(!is->seek_req) {
    is->seek_pos = pos;
    is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
    is->seek_req = 1;
  }
}

int main(int argc, char *argv[]) {

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  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);
  }

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
        screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  screen_mutex = SDL_CreateMutex();

  av_strlcpy(is->filename, argv[1], sizeof(is->filename));

  is->pictq_mutex = SDL_CreateMutex();
  is->pictq_cond = SDL_CreateCond();

  schedule_refresh(is, 40);

  is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
  is->parse_tid = SDL_CreateThread(decode_thread, is);
  if(!is->parse_tid) {
    av_free(is);
    return -1;
  }

  av_init_packet(&flush_pkt);
  flush_pkt.data = "FLUSH";

  for(;;) {
    double incr, pos;
    SDL_WaitEvent(&event);
    switch(event.type) {
    case SDL_KEYDOWN:
      switch(event.key.keysym.sym) {
      case SDLK_LEFT:
	incr = -10.0;
	goto do_seek;
      case SDLK_RIGHT:
	incr = 10.0;
	goto do_seek;
      case SDLK_UP:
	incr = 60.0;
	goto do_seek;
      case SDLK_DOWN:
	incr = -60.0;
	goto do_seek;
      do_seek:
	if(global_video_state) {
	  pos = get_master_clock(global_video_state);
	  pos += incr;
	  stream_seek(global_video_state, (int64_t)(pos * AV_TIME_BASE), incr);
	}
	break;
      default:
	break;
      }
      break;
    case FF_QUIT_EVENT:
    case SDL_QUIT:
      is->quit = 1;
      /*
       * If the video has finished playing, then both the picture and
       * audio queues are waiting for more data.  Make them stop
       * waiting and terminate normally.
       */
      SDL_CondSignal(is->audioq.cond);
      SDL_CondSignal(is->videoq.cond);
      SDL_Quit();
      return 0;
      break;
    case FF_REFRESH_EVENT:
      video_refresh_timer(event.user.data1);
      break;
    default:
      break;
    }
  }
  return 0;

}

Обработка команды поиска


Теперь мы собираемся добавить некоторые возможности для поиска в нашем плеере, потому что это реально раздражает, когда нельзя перемотать фильм назад. Кроме того, мы увидим, насколько легко использовать функцию av_seek_frame.

Мы собираемся сделать так, чтобы стрелки на клавиатуре «влево» и «вправо» прокручивали фильм вперед и назад немного, а стрелки «вверх» и «вниз» — уже более существенно. «Немного» — это будет 10 секунд, а «много» — все 60. Поэтому нам нужно настроить наш основной цикл, чтобы он перехватывал события нажатия клавиш. Но дело в том, что когда мы получаем нажатие клавиши, мы не можем вызвать av_seek_frame напрямую. Это сделать надо сделать в нашем основном цикле декодирования, цикле decode_thread. Поэтому вместо этого мы добавим некоторые значения в основную структуру, которая будет содержать новую позицию для поиска и некоторые флаги поиска:

  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

Теперь нам нужно настроить наш основной цикл, отлавливающий нажатия клавиш:

  for(;;) {
    double incr, pos;

    SDL_WaitEvent(&event);
    switch(event.type) {
    case SDL_KEYDOWN:
      switch(event.key.keysym.sym) {
      case SDLK_LEFT:
	incr = -10.0;
	goto do_seek;
      case SDLK_RIGHT:
	incr = 10.0;
	goto do_seek;
      case SDLK_UP:
	incr = 60.0;
	goto do_seek;
      case SDLK_DOWN:
	incr = -60.0;
	goto do_seek;
      do_seek:
	if(global_video_state) {
	  pos = get_master_clock(global_video_state);
	  pos += incr;
	  stream_seek(global_video_state, 
                      (int64_t)(pos * AV_TIME_BASE), incr);
	}
	break;
      default:
	break;
      }
      break;

Чтобы поймать нажатие клавиши, сначала смотрим, произошло ли мы событие SDL_KEYDOWN. Затем проверяем, какой ключ получен, используя event.key.keysym.sym. Как только мы узнаем, в каком направлении ищем, вычисляем новое время, добавляя приращение к значению из нашей новой функции get_master_clock. Затем вызываем функцию stream_seek для установки значений seek_pos и т.д. Конвертируем наше новое время в единицы внутренней метки времени avcodec. Напомним, что временные метки в потоках измеряются в кадрах, а не в секундах, по такой формуле: секунды = кадры * time_base(fps). По умолчанию для avcodec установлено значение 1000000 кадров в секунду (таким образом, позиция в 2 секунды будет иметь метку времени 2000000). Почему нам нужно преобразовать это значение — увидим позже.

Вот наша функция stream_seek. Обратите внимание, что мы устанавливаем флаг, если идем назад:

void stream_seek(VideoState *is, int64_t pos, int rel) {

  if(!is->seek_req) {
    is->seek_pos = pos;
    is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
    is->seek_req = 1;
  }
}

Теперь давайте перейдем к нашей decode_thread, где фактически и выполним поиск. В исходных файлах можно заметить, что мы пометили область «здесь идёт поиск». Ну, мы собираемся прямо сейчас это там поместить.

Поиск сосредоточен вокруг функции av_seek_frame. Эта функция принимает в качестве аргумента форматный контекст, поток, временную метку и набор флагов. Функция будет искать метку времени, которую вы ей дадите. Единицей отметки времени является time_base потока, который вы передаёте функции. Тем не менее, вам не нужно передавать его в поток (обозначается передачей значения -1). Если вы это сделаете, time_base будет находиться во внутренней единице времени avcodec или 1000000fps. Вот почему мы умножили нашу позицию на AV_TIME_BASE, когда установили seek_pos.

Однако иногда вы можете (редко) столкнуться с проблемами для некоторых файлов, если передаёте av_seek_frame − 1 для потока, поэтому мы собираемся выбрать первый поток в нашем файле и передать его av_seek_frame. Не забывайте, что мы должны изменить масштаб нашей метки времени, чтобы оказаться в новой «системе координат».

if(is->seek_req) {
  int stream_index= -1;
  int64_t seek_target = is->seek_pos;

  if     (is->videoStream >= 0) stream_index = is->videoStream;
  else if(is->audioStream >= 0) stream_index = is->audioStream;

  if(stream_index>=0){
    seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
                      pFormatCtx->streams[stream_index]->time_base);
  }
  if(av_seek_frame(is->pFormatCtx, stream_index, 
                    seek_target, is->seek_flags) < 0) {
    fprintf(stderr, "%s: error while seeking\n",
            is->pFormatCtx->filename);
  } else {
     /* handle packet queues... more later... */

av_rescale_q(a, b, c) — это функция, которая масштабирует временну́ю метку от одной базы к другой. Он в основном вычисляет a * b / c, но эта функция пригодится, потому что это вычисление иногда приводит к переполнению. AV_TIME_BASE_Q является дробной версией AV_TIME_BASE. Они совершенно разные: AV_TIME_BASE * time_in_seconds = avcodec_timestamp и AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (но обратите внимание, что AV_TIME_BASE_Q на самом деле является объектом AVRational, поэтому нужно использовать специальные функции q в avcodec для его обработки).

Очистка буферов


Итак, мы правильно настроили наши поиски, но пока ещё не закончили. Припоминаете, у нас ведь есть очередь, настроенная для накопления пакетов? Теперь, когда мы в другой метке времени, нужно очистить эту очередь, иначе поиск в фильме не будет работать! Кроме того, avcodec имеет свои собственные внутренние буферы, которые также должны быть очищены для каждого потока.

Для этого нужно сначала написать функцию, очищающую нашу очередь пакетов. Затем нужно каким-то образом проинструктировать аудио- и видеопоток, что они очищали внутренние буферы avcodec. Мы можем сделать это, поместив специальный пакет в очередь после его очистки, и когда они (потоки) обнаружат этот специальный пакет, они просто очистят свои буферы.

Давайте начнём с функции сброса. Это действительно довольно просто, поэтому я просто покажу вам код:

static void packet_queue_flush(PacketQueue *q) {
  AVPacketList *pkt, *pkt1;

  SDL_LockMutex(q->mutex);
  for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
    pkt1 = pkt->next;
    av_free_packet(&pkt->pkt);
    av_freep(&pkt);
  }
  q->last_pkt = NULL;
  q->first_pkt = NULL;
  q->nb_packets = 0;
  q->size = 0;
  SDL_UnlockMutex(q->mutex);
}

Теперь, когда очередь очищена, добавим наш «очищающий пакет». Но сначала неплохо бы определить, что это такое, и создать его:

AVPacket flush_pkt;

main() {
  ...
  av_init_packet(&flush_pkt);
  flush_pkt.data = "FLUSH";
  ...
}

Теперь помещаем этот пакет в очередь:

  } else {
    if(is->audioStream >= 0) {
      packet_queue_flush(&is->audioq);
      packet_queue_put(&is->audioq, &flush_pkt);
    }
    if(is->videoStream >= 0) {
      packet_queue_flush(&is->videoq);
      packet_queue_put(&is->videoq, &flush_pkt);
    }
  }
  is->seek_req = 0;
}

(Этот фрагмент кода продолжает приведенный выше фрагмент кода для decode_thread.) Нам также нужно изменить packet_queue_put, чтобы мы не дублировали специальный пакет для очистки:

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
    return -1;
  }

И затем в аудио- и видеопотоке мы помещаем этот вызов в avcodec_flush_buffers сразу после packet_queue_get:

    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    if(pkt->data == flush_pkt.data) {
      avcodec_flush_buffers(is->audio_st->codec);
      continue;
    }

Приведенный выше фрагмент кода точно такой же для видеопотока, с заменой «audio» на «video».

Это самое оно! Мы сделали это! Скомпилируйте ваш плеер:

gcc -o tutorial07 tutorial07.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

и наслаждайтесь вашим киноплеером, сделанным менее чем за 1000 строк C!

Хотя, конечно, остаётся куча вещей, которые можно добавить или улучшить.






Послесловие


Итак, у нас получился работающий плеер, но он, конечно, не так хорош, каким мог бы быть. Можно было бы ещё доработать напильником и добавить много чего полезного:

  • Посмотрим правде в глаза, этот плеер — отстой. Та версия ffplay.c, на которой он основан, полностью устарела, и, как следствие, этот учебник нуждается в основательной переработке. Если вы хотите перейти к более серьёзным проектам, использующим библиотеки FFmpeg, я настойчиво рекомендую в качестве следующей задачи проверить самую последнюю версию ffplay.c.
  • Обработка ошибок в нашем коде ужасна и может быть реализована гораздо лучше.
  • Мы не можем поставить фильм на паузу, а это, вне всяких сомнений, одна из самых полезных функций. Это можно реализовать, используя внутреннюю переменную paused в нашей общей структуре, которую мы устанавливаем, когда пользователь делает паузу. Затем нужно проконтролировать, чтобы в потоках аудио, видео и декодирования ничего не выводилось. Для поддержки сети используется av_read_play. Это всё довольно-таки просто на словах, но не очевидно для понимания. Так что, если хотите попробовать большего, отнеситесь к этому как к домашнему заданию. Подсказка: подсмотрите, как это сделано в ffplay.c.
  • Поддержка видеооборудования.
  • Побайтовый поиск. Если вы вычисляете искомую позицию в байтах, а не в секундах, это будет более точным для видеофайлов, имеющих несмежные метки времени, таких как VOB-файлы.
  • Сброс кадра. Если видеоряд тормозит, вместо более частого обновления стоит отбрасывать последующие кадры.
  • Сетевая поддержка. Этот видеоплеер не может воспроизводить потоковое видео.
  • Есть некоторые опции, которые нужно установить, если нужно, чтобы наш проигрыватель поддерживал необработанное видео, вроде файлов YUV, так как в таких случаях мы не имеем возможности предугадать размер или time_base.
  • Полноэкранный режим.
  • Всякое-прочее-разное, например разные форматы картинок; смотрите ffplay.c для всех переключателей в командной строке.


Если вы хотите узнать больше про FFmpeg, то тут мы рассмотрели далеко не всё. Следующим шагом рекомендую изучить кодирование мультимедиа. Оптимально начать с файла output_example.c, который найдёте в дистрибутиве FFmpeg. Я мог бы написать ещё один учебник уже на эту тему, но вряд ли он превзойдёт данное руководство.

UPD. Давненько я не обновлял этот текст, а между тем мир не стоит на месте. Этот учебник требует только простых обновлений API; очень мало что поменялось в плане основных концепций. Большинство этих обновлений фактически упростили код. Тем не менее, хотя я прошёлся по всему коду и обновил его, FFplay все также превосходит этот игрушечный плеер. Положа руку на сердце, признаем: в этих уроках мы написали довольно-таки паршивый проигрыватель для фильмов. Поэтому, если вы сегодня (или в будущем) пожелаете улучшить этот учебник, рекомендую ознакомиться с FFplay и выяснить, чего не хватает. Полагаю, что это в основном касается использования видеооборудования, но, вполне возможно, я упускаю ещё какие-то очевидные вещи. Возможно, сравнение с текущим FFplay привело бы к кардинальному переписыванию некоторых вещей — я пока не смотрел.

Но я очень горд тем, что за эти годы мой труд помог очень многим, даже с учётом того, что зачастую код люди искали в других местах. Я безмерно благодарен Челяеву, который взял на себя рутину по замене всех функций, которые устарели с тех пор, как я написал эту монографию 8(!) лет назад.

Тешу себя надеждой, что эти уроки получились полезными и нескучными. Если есть какие-либо предложения, ошибки, жалобы, благодарности и т.д., касающиеся данного руководства, пожалуйста, напишите мне по адресу dranger собака gmail дот com. И да, нет смысла просить меня помочь с вашим FFmpeg-проектом. Подобных писем слишком много.






Приложение 1. Список функций


int avformat_open_input(AVFormatContext **ptr, const char * filename, AVInputFormat *fmt, AVDictionary **options)

Открывает имя медиа-файла, сохраняет контекст формата в адресе, указанном в ptr.

fmt: если не NULL, то устанавливает формат файла.
buf_size: размер буфера (опционально).
options: AVDictionary заполняется параметрами AVFormatContext и демультиплексора.

void avformat_close_input(AVFormatContext **s)

Закрывает медиа-файл. Однако не закрывает кодеки.

nt avio_open2 (AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options)

Создает контекст ввода-вывода для использования ресурса, указанного в url.

s: указатель на место, где будет создан AVIOContext. В случае сбоя указанное значение устанавливается в NULL.
url: имя ресурса для доступа.
flags: управляют открытием ресурса, указанного в url.
int_cb: обратный вызов прерывания для использования на уровне протоколов.
options: словарь, заполненный частными параметрами протокола. При возврате функции параметр будет уничтожен и заменен на dict, содержащий опции, которые не были найдены. Может быть NULL.

int av_dup_packet(AVPacket *pkt)

Само собой, это хак: если данный пакет не был выделен, мы размещаем его сюда. Возвращает 0 в случае успеха или AVERROR_NOMEM в случае неудачи.

int av_find_stream_info(AVFormatContext *s, AVDictionary **options)

Эта функция ищет неочевидную информацию о потоке, вроде частоты кадров. Это полезно для форматов файлов без заголовков, таких как MPEG. Рекомендуется вызывать после открытия файла. Возвращает >= 0 в случае успеха, AVERROR_ * в случае ошибки.

AVFrame *avcodec_free_frame()

Старое имя для av_frame_free. Изменено в lavc 55.28.1.

void av_frame_free (AVFrame **frame)

Освобождает кадр и любые динамически размещенные в нём объекты, например, extended_data.

void av_free(void *ptr)

Освобождает память, выделенную с помощью av_malloc() или av_realloc(). Можно вызывать эту функцию с ptr == NULL. Рекомендуется вместо этого вызывать av_freep().

void av_freep(void *ptr)

Освобождает память и устанавливает указатель в NULL. Внутренне использует av_free().

void av_free_packet(AVPacket *pkt)

Обёртка вокруг метода уничтожения пакета (pkt->destruct).

int64_t av_gettime()

Получить текущее время в микросекундах.

void av_init_packet(AVPacket *pkt)

Инициализация необязательных полей пакета.

void *av_malloc(unsigned int size)

Выделение памяти по размеру байта с выравниванием, подходящим для всех обращений к памяти (включая векторы, если они доступны на ЦП). av_malloc(0) должен возвращать ненулевой указатель.

void *av_mallocz(unsigned int size)

То же, что av_malloc(), но инициализирует память в нулевое значение.

double av_q2d(AVRational a)

Удваивает AVRational.

int av_read_frame(AVFormatContext *s, AVPacket *pkt)

Возвращает следующий кадр потока. Информация хранится в виде пакета в pkt.

Возвращенный пакет действителен до следующего av_read_frame() или до av_close_input_file() и должен быть освобождён с помощью av_free_packet. Для видео пакет содержит ровно один кадр. Для аудио он содержит целое число кадров, если каждый кадр имеет известный фиксированный размер (например, данные PCM или ADPCM). Если аудиокадры имеют переменный размер (например, аудио MPEG), то он содержит один кадр.

pkt->pts, pkt->dts и pkt->duration всегда устанавливаются на правильные значения в единицах AVStream.timebase (и предполагается, что формат не может их предоставить). pkt->pts может быть AV_NOPTS_VALUE, если формат видео имеет B-кадры, поэтому лучше полагаться на pkt->dts, если вы не распаковываете полезную нагрузку.

Возвращаемый результат: 0, если все в порядке, < 0, если ошибка или конец файла.

void av_register_all();

Регистрирует все кодеки в библиотеке.

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

Возвращает a * bq / cq.

int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)

Ищет ключевой кадр на отметке времени.

stream_index: если stream_index равен -1, выбирается поток по умолчанию, и временна́я метка автоматически преобразуется из единиц AV_TIME_BASE в специфичную для потока time_base.
timestamp: временна́я метка измеряемая в единицах AVStream.time_base или, если поток не указан, то в единицах AV_TIME_BASE.
flags: установите параметры, касающиеся направления и режима поиска:
AVSEEK_FLAG_ANY: искать в любом кадре, а не только в ключевых.
AVSEEK_FLAG_BACKWARD: искать в обратном направлении.
AVSEEK_FLAG_BYTE: поиск на основе позиции в байтах.

AVFrame *avcodec_alloc_frame()

Старое имя для av_frame_alloc. Изменено в lavc 55.28.1.

AVFrame *av_frame_alloc()

Выделяет AVFrame и инициализирует его. Может быть освобождено с помощью av_frame_free().

int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, const AVPacket *avpkt)

Декодирует аудиокадр из avpkt в кадр. Функция avcodec_decode_audio4() декодирует аудиофайл из AVPacket. Для его декодирования используется аудиокодек, который был связан с avctx с помощью avcodec_open2(). Результирующий декодированный кадр сохраняется в заданном AVFrame. Если кадр был распакован, он установит got_frame_ptr в 1.

Предупреждение: входной буфер, avpkt->data, должен быть на FF_INPUT_BUFFER_PADDING_SIZE больше, чем фактические байты чтения, потому что некоторые оптимизированные считыватели битового потока читают 32 или 64 бита за раз и могут читать до конца.

avctx: контекст кодека.
frame: целевой кадр.
got_frame_ptr: целевой int, который будет установлен, если кадр был распакован.
AVPKT: AVPacket, содержащий аудио.

Возвращаемый результат: при ошибке возвращается отрицательное значение, в противном случае возвращается количество байтов, использованных из входного AVPacket.

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *frameFinished, const AVPacket *avpkt)

Декодирует видеокадр из buf в изображение. Функция avcodec_decode_video2() декодирует видеокадр из буфера входного буфера размером buf_size. Для его декодирования используется видеокодек, который был связан с avctx с помощью avcodec_open2(). Полученный декодированный кадр сохраняется в картинке.

Предупреждение: примеры выравнивания и проблемы с буфером, которые относятся к avcodec_decode_audio4, применимы и к этой функции.

avctx: контекст кодека.
picture: AVFrame, в котором будет сохранено декодированное видео.
frameFinished: ноль, если ни один кадр не может быть распакован, иначе не равно нулю.
avpkt: входной AVPacket, содержащий входной буфер. Можно создать такой пакет с помощью av_init_packet(), затем, задав данные и размер, некоторые декодеры могут дополнительно нуждаться в других полях, таких как flags&AV_PKT_FLAG_KEY. Все декодеры разработаны так, чтобы использовать как можно меньше полей.

Возвращаемый результат: При ошибке возвращается отрицательное значение, в противном случае используется количество байтов или ноль, если ни один кадр не может быть распакован.

int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)

Простой метод доступа для получения best_effort_timestamp из объекта AVFrame.

AVCodec *avcodec_find_decoder(enum CodecID id)

Ищет декодер с идентификатором CodecID. Возвращает NULL при ошибке. Следует вызывать после получения требуемого AVCodecContext из потока в AVFormatContext, используя codecCtx->codec_id.

void avcodec_flush_buffers(AVCodecContetx *avctx)

Очистка буфера. Вызывается при поиске или переключении на другой поток.

AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)

Назначает AVCodecContext и устанавливает для его полей значения по умолчанию.

int avcodec_copy_context (AVCodecContext *dest, const AVCodecContext *src)

Копирование настроек исходного AVCodecContext в целевой AVCodecContext. Результирующий контекст кодека назначения будет закрыт, т.е. необходимо вызвать avcodec_open2(), прежде чем вы использовать этот AVCodecContext для декодирования/кодирования видео/аудио данные.

dest: должен быть инициализирован с помощью avcodec_alloc_context3(NULL), в противном случае будет неинициализирован.

int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options)

Инициализирует avctx для использования кодека, указанного в codec. Следует использовать после avcodec_find_decoder. Возвращает ноль при успехе и отрицательное значение при ошибке.

int avpicture_fill(AVPicture *picture, uint8_t *ptr, int pix_fmt, int width, int height)

Устанавливает структуру, на которую указывает картинка, с буфером ptr, форматом pix_fmt и заданными шириной и высотой. Возвращает размер данных изображения в байтах.

int avpicture_get_size(int pix_fmt, int width, int height)

Вычисляет, сколько байтов потребуется для изображения заданной ширины, высоты и формата изображения.

struct SwsContext* sws_getContext(int srcW, int srcH, int srcFormat, int dstW, int dstH, int dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, double *param)

Возвращает SwsContext для использования в sws_scale.

srcW, srcH, srcFormat: ширина, высота и формат искомых пикселей.
dstW, dstH, dstFormat: ширина, высота и формат конечных пикселей.
flags: метод масштабирования для использования.
Доступны следующие варианты: SWS_FAST_BILINEAR, SWS_BILINEAR, SWS_BICUBIC, SWS_X, SWS_POINT, SWS_AREA, SWS_BICUBLIN, SWS_GAUSS, SWS_SINC, SWS_LANCZOS, SWS_SPLINE.
Другие флаги включают в себя флаги возможностей ЦП: SWS_CPU_CAPS_MMX, SWS_CPU_CAPS_MMX2, SWS_CPU_CAPS_3DNOW, SWS_CPU_CAPS_ALTIVEC.
Другие флаги включают (в настоящее время не полностью реализованы) SWS_FULL_CHR_H_INT, SWS_FULL_CHR_H_INP и SWS_DIRECT_BGR.
Наконец, есть SWS_ACCURATE_RND и, возможно, самый полезный для начинающих, SWS_PRINT_INFO.
Я понятия не имею, что делает большинство из них. Может быть, напишите мне?
srcFilter, dstFilter: SwsFilter для источника и назначения. SwsFilter включает фильтрацию цветности/яркости. Значение NULL по умолчанию.
param: должен быть указателем на буфер int[2] с коэффициентами. Не задокументирован. Похоже, используется для небольшого изменения стандартных алгоритмов масштабирования. Значение NULL по умолчанию. Только для экспертов!

int sws_scale(SwsContext *c, uint8_t *src, int srcStride[], int srcSliceY, int srcSliceH, uint8_t dst[], int dstStride[]
sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, is->video_st->codec->height, pict.data, pict.linesize);

Масштабирует данные в src в соответствии с нашими настройками в нашем SwsContext*c.
srcStride и dstStride — это размер строки источника и назначения.

SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param)

Добавляет функцию обратного вызова, запускаемую по истечении указанного количества миллисекунд. Функция обратного вызова передает текущий интервал таймера и предоставленный пользователем параметр из вызова SDL_AddTimer и возвращает следующий интервал таймера. (Если возвращаемое значение обратного вызова совпадает с переданным, таймер продолжает работать с той же скоростью.) Если возвращаемое значение обратного вызова равно 0, таймер отменяется.
Другой способ отменить текущий таймер — вызвать SDL_RemoveTimer с идентификатором таймера (который был возвращен из SDL_AddTimer).

Функция обратного вызова таймера может выполняться в другом потоке, чем ваша основная программа, и поэтому не должна вызывать какие-либо функции из себя. Однако всегда можно вызвать SDL_PushEvent.

Степень детализации таймера зависит от платформы, но нужно рассчитывать, что он составляет не менее 10 мс, поскольку это наиболее распространенное значение. Это означает, что если запросить таймер 16 мс, обратный вызов будет запущен примерно через 20 мс в незагруженной системе. Если нужно установить флаг, сигнализирующий об обновлении кадров со скоростью 30 кадров в секунду (каждые 33 мс), можно установить таймер на 30 мс (см. пример ниже). Если вы используете эту функцию, вам нужно передать SDL_INIT_TIMER в SDL_Init.

Возвращает значение идентификатора для добавленного таймера или NULL, если произошла ошибка.

Формат для обратного вызова:
Uint32 callback (интервал Uint32, void * param)


int SDL_CondSignal(SDL_cond *cond)

Перезапуск одного из потоков, ожидающих условной переменной cond. Возвращает 0 при успехе и -1 при ошибке.

int SDL_CondWait(SDL_cond *cond, SDL_mutex *mut);

Разблокируйте предоставленный мьютекс и подождите, пока другой поток вызовет SDL_CondSignal или SDL_CondBroadcast для условной переменной cond, затем повторно заблокируйте мьютекс. Мьютекс должен быть заблокирован перед входом в эту функцию. Возвращает 0 при получении сигнала или -1 при ошибке.

SDL_cond *SDL_CreateCond(void);

Создает переменную условия.

SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data);

SDL_CreateThread создает новый поток выполнения, который разделяет всю глобальную память своего родителя, обработчики сигналов, файловые дескрипторы и т.д. И запускает функцию fn, передавая ей данные void-указателя. Поток завершается, когда fn возвращает значение.

void SDL_Delay (Uint32 мс);

Ожидает указанное количество миллисекунд. SDL_Delay будет ждать как минимум указанное время, но возможно дольше из-за планирования ОС.
Примечание: рассчитывать на гранулярность задержки не менее 10 мс. Некоторые платформы имеют более короткие такты, но это наиболее распространенный вариант.

SDL_Overlay *SDL_CreateYUVOverlay(int width, int height, Uint32 format, SDL_Surface *display);

SDL_CreateYUVOverlay создает YUV-наложение указанной ширины, высоты и формата (список доступных форматов см. в структуре данных SDL_Overlay) для предоставленного отображения. Возвращает SDL_Overlay.

display должен фактически быть поверхностью, полученной из SDL_SetVideoMode, в противном случае эта функция будет работать по умолчанию.

Термин «наложение» является неправильным, поскольку, если наложение не создано аппаратно, содержимое поверхности отображения под областью, где отображается наложение, будет перезаписано при отображении наложения.

int SDL_LockYUVOverlay(SDL_Overlay *overlay)

SDL_LockYUVOverlay блокирует наложение для прямого доступа к данным пикселей. Возвращает 0 в случае успеха или -1 в случае ошибки.

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)

Разблокирует ранее заблокированное наложение. Наложение должно быть разблокировано, прежде чем его можно будет отобразить.

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)

Помещает наложение на поверхность, указанную при его создании. SDL_Rect-структура dstrect определяет позицию и размер конечного пункта. Если dstrect больше или меньше наложения, то наложение будет масштабировано, это оптимизировано для 2-кратного масштабирования. Возвращает 0 в случае успеха.

void SDL_FreeYUVOverlay(SDL_Overlay *overlay)

Освобождает наложение, созданное SDL_CreateYUVOverlay.

int SDL_Init(Uint32 flags);

Инициализирует SDL. Это должно быть вызвано перед всеми другими SDL-функциями. Параметр flags указывает, какие части SDL инициализировать.

SDL_INIT_TIMER — инициализирует подсистему таймера.
SDL_INIT_AUDIO — инициализирует аудиоподсистему.
SDL_INIT_VIDEO — инициализирует видеоподсистему.
SDL_INIT_CDROM — инициализирует подсистему CD-ROM.
SDL_INIT_JOYSTICK — инициализирует подсистему джойстика.
SDL_INIT_EVERYTHING — инициализирует всё вышеперечисленное.
SDL_INIT_NOPARACHUTE — не позволяет SDL отлавливать фатальные ошибки.
SDL_INIT_EVENTTHREAD — запускает менеджер событий в отдельном потоке.

Возвращает -1 в случае ошибки или 0 в случае успеха. Можно получить расширенное сообщение об ошибке, вызвав SDL_GetError. Типичная причина ошибки — использование определённого дисплея без соответствующей поддержки подсистемы, например отсутствие драйвера мыши при использовании с устройством буфера кадра. В этом случае можно либо скомпилировать SDL без мыши, либо установить переменную среды «SDL_NOMOUSE = 1» перед запуском приложения.

SDL_mutex *SDL_CreateMutex(void);

Создает новый, незаблокированный мьютекс.

int SDL_LockMutex(SDL_mutex *mutex)

SDL_LockMutex — это псевдоним для SDL_mutexP. Он блокирует мьютекс, который был ранее создан с помощью SDL_CreateMutex. Если мьютекс уже заблокирован другим потоком, то SDL_mutexP не возвращает значение, пока заблокированный им поток не разблокирует его (с помощью SDL_mutexV). При повторном вызове мьютекса SDL_mutexV (a.k.a. SDL_UnlockMutex) должен вызываться равное количество раз, чтобы вернуть мьютекс в разблокированное состояние. Возвращает 0 в случае успеха или -1 в случае ошибки.

int SDL_UnlockMutex(SDL_Mutex *mutex)

Разблокировка мьютекса.

int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

Эта функция открывает аудиоустройство с требуемыми параметрами и возвращает 0 в случае успеха, помещая фактические аппаратные параметры в структуру, на которую в итоге указывает. Если получено значение NULL, аудиоданные, передаваемые в функцию обратного вызова, будут гарантированно иметь требуемый формат и при необходимости будут автоматически преобразованы в аппаратный аудиоформат. Эта функция возвращает -1, если не удалось открыть аудиоустройство или не удалось настроить аудиопоток.

Чтобы открыть аудиоустройство, необходимо создать нужный SDL_AudioSpec. Затем нужно заполнить эту структуру желаемыми аудио спецификациями.

desired->freq: требуемая частота звука в сэмплах в секунду.
desired->format: требуемый аудиоформат (см. SDL_AudioSpec).
desired->channels: требуемые каналы (1 для моно, 2 для стерео, 4 для объёмного звучания, 6 для объёмного звучания с центровкой и LFE).
desired->samples: требуемый размер аудиобуфера в сэмплах. Это число должно быть степенью двойки и может быть отрегулировано аудиодрайвером до значения, более подходящего для аппаратного обеспечения. Оптимальные значения колеблются от 512 до 8192 включительно, в зависимости от приложения и скорости процессора. Меньшие значения приводят к более быстрому времени отклика, но при этом могут привести к снижению производительности, если приложение выполняет тяжёлую обработку и не может вовремя заполнить аудио-буфер. Стереосэмпл состоит из правого и левого каналов в LR-порядке. Обратите внимание, что количество сэмплов напрямую связано со временем по следующей формуле: ms = (samples * 1000) / freq.
desired->callback: должно быть установлено на функцию, которая будет вызываться, когда аудиоустройство готово для получения дополнительных данных. Передаётся указатель на аудиобуфер и длина в байтах аудиобуфера. Эта функция обычно выполняется в отдельном потоке, и поэтому необходимо защитить структуры данных, к которым она обращается, вызывая SDL_LockAudio и SDL_UnlockAudio в коде. Прототипом обратного вызова является void callback (void * userdata, Uint8 * stream, int len). userdata — указатель, хранящийся в поле userdata SDL_AudioSpec. stream — указатель на аудиобуфер, который вы хотите заполнить информацией, а len — длина аудиобуфера в байтах.
required->userdata: этот указатель передаётся в качестве первого параметра в функцию обратного вызова.

SDL_OpenAudio считывает эти поля из желаемой структуры SDL_AudioSpec, переданной в функцию, и пытается найти аудио-конфигурацию, соответствующую вашему желанию. Как упомянуто выше, если полученный параметр равен NULL, то SDL с преобразованием из желаемых настроек звука в настройки оборудования во время воспроизведения.

Если возвращается NULL, тогда требуемый SDL_AudioSpec — это ваша рабочая спецификация, в противном случае полученный SDL_AudioSpec становится рабочей спецификацией, и желаемая спецификация может быть удалена. Данные в рабочей спецификации используются при построении SDL_AudioCVT для преобразования загруженных данных в формат оборудования.

SDL_OpenAudio вычисляет поля размера и тишины как для желаемой, так и для полученной спецификации. Поле размера хранит общий размер аудио буфера в байтах, в то время как silence хранит значение, используемое для представления тишины в аудио буфере

Звуковое устройство начинает воспроизведение тишины, когда оно открыто, и его следует включить для воспроизведения, вызвав SDL_PauseAudio(0), когда вы будете готовы к вызову функции обратного вызова аудио. Поскольку аудиодрайвер может изменять запрошенный размер аудиобуфера, надо выделить любые локальные микшерные буферы после открытия аудиоустройства.

void SDL_PauseAudio(int pause_on)

Эта функция приостанавливает и останавливает обработку звукового обратного вызова. Он должен вызываться с pause_on = 0 после открытия аудиоустройства, чтобы начать воспроизведение звука. Это позволяет безопасно инициализировать данные для функции обратного вызова после открытия аудиоустройства. Тишина будет записана на аудиоустройство во время паузы.

int SDL_PushEvent(SDL_Event *event)

Очередь событий, фактически используемая как двусторонний канал связи. Не только события могут быть прочитаны из очереди, но пользователь также может помещать в неё свои собственные события. Событие — это указатель на структуру события, которую вы хотите поместить в очередь. Событие копируется в очередь, и вызывающая сторона может распоряжаться памятью, на которую указывает после возврата SDL_PushEvent. Эта функция является поточно-ориентированной и может безопасно вызываться из других потоков. Возвращает 0 в случае успеха или -1, если событие не может быть отправлено.

int SDL_WaitEvent(SDL_Event *event)

Ожидает в течение неопределенного времени следующего доступного события, возвращая 0, если при ожидании событий произошла ошибка, 1 в противном случае. Если событие не равно NULL, следующее событие удаляется из очереди и сохраняется в этой области.

void SDL_Quit()

Отключает все подсистемы SDL и освобождает выделенные им ресурсы. Это всегда следует вызывать перед выходом.

SDL_Surface *SDL_SetVideoMode(int width, int height, int bitsperpixel, Uint32 flags)

Настройка видеорежима с указанной шириной, высотой и битами пикселей. Начиная с SDL 1.2.10, если ширина и высота равны 0, он будет использовать ширину и высоту текущего режима видео (или режима рабочего стола, если режим не установлен). Если bitsperpixel равен 0, обрабатывается как текущие биты отображения на пиксель. Параметр flags такой же, как поле flags структуры SDL_Surface. Или комбинации следующих значений:

SDL_SWSURFACE — создать видеоповерхность в системной памяти.
SDL_HWSURFACE — создать видеоповерхность в видеопамяти.
SDL_ASYNCBLIT — включить использование асинхронных обновлений поверхности дисплея. Обычно это замедляет работу на однопроцессорных компьютерах, но может повысить скорость в системах SMP.
SDL_ANYFORMAT — обычно, если видеоповерхность с запрошенными битами на пиксель (bpp — от bits-per-pixel) недоступна, SDL будет эмулировать видео с затенённой поверхностью. Передача SDL_ANYFORMAT предотвращает это и заставляет SDL использовать поверхность видео, независимо от его глубины в пикселях.
SDL_HWPALETTE — предоставить SDL эксклюзивный доступ к палитре. Без этого флага вы не всегда можете получить цвета, которые вы запрашиваете с помощью SDL_SetColors или SDL_SetPalette.
SDL_DOUBLEBUF — включить аппаратную двойную буферизацию; действительно только с SDL_HWSURFACE. Вызов SDL_Flip обратит буферы и обновит экран. Всё рисование будет происходить на поверхности, которая не отображается в данный момент. Если двойная буферизация не может быть включена, то SDL_Flip просто выполнит SDL_UpdateRect на весь экран.
SDL_FULLSCREEN SDL — попытаться использовать полноэкранный режим. Если изменение аппаратного разрешения невозможно (по какой-либо причине), будет использовано следующее более высокое разрешение, а окно дисплея будет центрировано на чёрном фоне.
SDL_OPENGL — создать контекст рендеринга OpenGL. Предполагается, что предварительно установлены атрибуты видео OpenGL с SDL_GL_SetAttribute.
SDL_OPENGLBLIT — создать контекст рендеринга OpenGL, как описано выше, но разрешить обычные операции блитинга. Поверхность экрана (2D) может иметь альфа-канал, и SDL_UpdateRects должны использоваться для обновления изменений поверхности экрана. ПРИМЕЧАНИЕ. Этот параметр сохраняется только для совместимости и будет удалён в следующих версиях. Не рекомендуется использовать в новом коде.
SDL_RESIZABL -создать окно с изменяемым размером. Когда размер окна изменяется пользователем, генерируется событие SDL_VIDEORESIZE, и SDL_SetVideoMode может быть вызван снова с новым размером.
SDL_NOFRAME Если возможно, SDL_NOFRAME заставляет SDL создать окно без заголовка или оформленный в рамку. Этот флаг автоматически устанавливается в полноэкранном режиме.
Примечание. Независимо от того, какие флаги SDL_SetVideoMode может удовлетворить, они устанавливаются в элементе flags возвращаемой поверхности.
ПРИМЕЧАНИЕ. Битовый пиксель 24 использует упакованное представление 3 байта на пиксель. Для более распространенного режима 4 байта на пиксель используйте битовый пиксель 32. Как ни странно, и 15, и 16 будут запрашивать режим 2 байта на пиксель, но с разными форматами пикселей.
ПРИМЕЧАНИЕ. Используйте SDL_SWSURFACE, если вы планируете выполнять отдельные пиксельные манипуляции или перетаскивать поверхности с помощью альфа-каналов и требовать высокой частоты кадров. Когда вы используете аппаратные поверхности (SDL_HWSURFACE), SDL копирует поверхности из видеопамяти в системную память, когда вы их блокируете, и обратно, когда вы их разблокировываете. Это может привести к значительному снижению производительности. (Имейте в виду, что вы можете запросить аппаратную поверхность, но при этом получить программную поверхность. Многие платформы могут предоставить аппаратную поверхность только при использовании SDL_FULLSCREEN.) SDL_HWSURFACE лучше всего использовать, когда поверхности, которые вы будете блитировать, также могут храниться в видеопамяти.
ПРИМЕЧАНИЕ. Если вы хотите контролировать положение на экране при создании оконной поверхности, вы можете сделать это, установив переменные среды «SDL_VIDEO_CENTERED = center» или «SDL_VIDEO_WINDOW_POS = x, y». Вы можете установить их через SDL_putenv.

Возвращаемое значение: Поверхность кадрового буфера или NULL в случае сбоя. Возвращаемая поверхность освобождается SDL_Quit и не должна быть освобождена вызывающей стороной.
ПРИМЕЧАНИЕ. Это правило включает в себя последовательные вызовы SDL_SetVideoMode (т.е. изменение размера) — существующая поверхность будет освобождена автоматически.






Приложение 2. Структуры данных



AVCodecContext

Вся информация о кодеке из потока, из AVStream->codec. Некоторые важные атрибуты:

AVRational time_base: количество кадров в секунду
int sample_rate: выборок в секунду
int channel: количество каналов

Полный список (очень внушительный) см. здесь (веб-архив, так как в оригинале приведена уже несуществующая ссылка). Многие параметры используются в основном для кодирования, а не для декодирования.

AVFormatContext

Поля данных:

const AVClass * av_class
AVInputFormat * iformat
AVOutputFormat * oformat
void * priv_data:
ByteIOContext pb: используется для низкоуровневой манипуляции с файлом.
unsigned int nb_streams: количество потоков в файле.
AVStream * streams [MAX_STREAMS]: данные для каждого потока хранятся здесь.
char filename [1024]: ну а как же без этого (в оригинале — duh).

Информация о файле:
int64_t timestamp:
char title[512]:
char author[512]:
char copyright[512]:
char comment[512]:
char album[512]:
int year:
int track:
char genre[32]:

int ctx_flags:
Возможные значения — AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, AVFMT_RAWPICTURE, AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_GENERIC_INDEX
AVPacketList * packet_buffer: Этот буфер нужен только тогда, когда пакеты уже буферизованы, но не декодированы, например, для получения параметров кодека в потоках mpeg.
int64_t start_time: при декодировании: позиция первого кадра компонента, в долях секунды AV_TIME_BASE. НИКОГДА не устанавливайте это значение напрямую: оно выводится из значений AVStream.
int64_t duration: decoding: длительность потока, в долях AV_TIME_BASE. НИКОГДА не устанавливайте это значение напрямую: оно выводится из значений AVStream.
int64_t file_size: общий размер файла, 0, если неизвестно.
int bit_rate: декодирование: общий битрейт потока в бит/с, 0, если недоступен. НИКОГДА не устанавливайте его напрямую, если file_size и длительность, известные в ffmpeg, могут вычислить его автоматически.
AVStream * cur_st
const uint8_t * cur_ptr
int cur_len
AVPacket cur_pkt:
int64_t data_offset:
int index_built: смещение первого пакета.
int mux_rate:
int packet_size:
int preload:
int max_delay:
int loop_output: количество циклов вывода в поддерживаемых форматах.
int flags:
int loop_input:
unsigned int probesize: декодирование: размер данных образца; в кодировании не используется.
int max_analyze_duration: максимальная продолжительность в единицах AV_TIME_BASE, в течение которой входные данные должны быть проанализированы в av_find_stream_info()
const uint8_t * key:
int keylen:

AVIOContext

Контекст ввода-вывода для доступа к ресурсам.

const AVClass * av_class: класс для приватных настроек.
unsigned char * buffer: начало буфера.
int buffer_size: максимальный размер буфера.
unsigned char * buf_ptr: текущая позиция в буфере.
unsigned char * buf_end: Данные могут быть меньше размером, чем buffer + buffer_size, если функция чтения вернула меньше данных, чем запрошено, к примеру.
void * opaque: закрытый указатель, переданный на чтение/запись/поиск/…
int(* read_packet )(void *opaque, uint8_t *buf, int buf_size):
int(* write_packet )(void *opaque, uint8_t *buf, int buf_size):
int64_t(* seek )(void *opaque, int64_t offset, int whence):
int64_t pos: позиция в файле текущего буфера.
int must_flush: true, если следующий поиск должен сбрасываться.
int eof_reached: true, если достигнут конец файла.
int write_flag: true, если открыто для записи.
int max_packet_size:
unsigned long checksum:
unsigned char * checksum_ptr:
unsigned long(* update_checksum )(unsigned long checksum, const uint8_t *buf, unsigned int size):
int error: содержит код ошибки или 0, если ошибки не произошло.
int(* read_pause )(void *opaque, int pause): приостановка или возобновление воспроизведения для сетевых потоковых протоколов, к примеру.
int64_t(* read_seek )(void *opaque, int stream_index, int64_t timestamp, int flags): поиск указанной метки времени в потоке с указанным индексом stream_index.
int seekable: комбинация флагов AVIO_SEEKABLE_ или 0, когда поток не доступен для поиска.
int64_t maxsize: максимальный размер файла, используется для ограничения выделения. Это поле является внутренним для libavformat, и доступ к нему извне запрещен.
int direct: avio_read и avio_write должны по возможности выполняться непосредственно, а не проходить через буфер, а avio_seek всегда будет напрямую вызывать основную функцию поиска.
int64_t bytes_read: статистика чтения байтов Это поле является внутренним для libavformat, и доступ к нему извне запрещен.
int seek_count: статистика поиска. Это поле является внутренним для libavformat, и доступ к нему извне запрещен.
int writeout_count: статистика записи. Это поле является внутренним для libavformat, и доступ к нему извне запрещен.
int orig_buffer_size: Исходный размер буфера, используемый внутри после проверки и обеспечения возврата для сброса размера буфера. Это поле является внутренним для libavformat, и доступ к нему извне запрещен.

AVDictionary

Используется для передачи параметров в ffmpeg.

int count:
AVDictionaryEntry *elems:

AVDictionaryEntry

Используется для хранения словарных записей в AVDictionary.

char *ket:
char *value:

AVFrame

Эта структура зависит от типа кодека и поэтому определяется динамически. Однако для этой структуры есть общие свойства и методы:

uint8_t *data[4]:
int linesize[4]: страйд информации.
uint8_t *base[4]:
int key_frame:
int pict_type:
int64_t pts: это не те pts, которые вы ожидаете при декодировании.
int coded_picture_number:
int display_picture_number:
int quality:
int age:
int reference:
int8_t *qscale_table:
int qstride:
uint8_t *mbskip_table:
int16_t (*motion_val[2])[2]:
uint32_t *mb_type:
uint8_t motion_subsample_log2:
void *opaque: пользовательские данные
uint64_t error[4]:
int type:
int repeat_pict: предписывает, что нужно повторить изображение указанное количество раз.
int qscale_type:
int interlaced_frame:
int top_field_first:
AVPanScan *pan_scan:
int palette_has_changed:
int buffer_hints:
short *dct_coeff:
int8_t *ref_index[2]:

AVPacket

Структура, в которой хранятся необработанные пакетные данные. Эти данные должны быть переданы avcodec_decode_audio2 или avcodec_decode_video, чтобы получить кадр.

int64_t pts: метка времени представления в единицах time_base.
int64_t dts: временная метка декомпрессии в единицах time_base.
uint8_t * data: необработанные данные.
int size: размер данных.
int stream_index: поток, из которого пришел AVPacket, основанный на количестве в AVFormatContext.
int flags: установливается значение PKT_FLAG_KEY, если пакет является ключевым кадром.
int duration: продолжительность представления в единицах time_base (0, если не доступно)
void(* destruct )(struct AVPacket *): функция освобождения ресурсов для этого пакета (по умолчанию av_destruct_packet).
void * priv:
int64_t pos: позиция байта в потоке, -1 если неизвестно.

AVPacketList

Простой связанный список для пакетов.

AVPacket pkt:
AVPacketList * next:

AVPicture

Эта структура точно такая же, как и первые два элемента данных AVFrame, поэтому её часто отбрасывают. Обычно используется в функциях SWS.

uint8_t * data [4]:
int linesize [4]: количество байтов в строке.

AVRational

Простая структура для представления рациональных чисел.

int num: числитель.
int den: знаменатель.

AVStream

Структура для потока. Вы, вероятно, будете использовать эту информацию в кодеке чаще всего.

int index:
int id:
AVCodecContext * codec:
AVRational r_frame_rate:
void * priv_data:
int64_t codec_info_duration:
int codec_info_nb_frames:
AVFrac pts:
AVRational time_base:
int pts_wrap_bits:
int stream_copy:
enum AVDiscard discard: можно выбрать пакеты, которые будут отброшены, поскольку не нуждаются в демультиплексировании.
float quality:
int64_t start_time:
int64_t duration:
char language [4]:
int need_parsing: 1 -> необходим полный парсинг, 2 -> парсить только заголовки, без перепаковки
AVCodecParserContext * parser:
int64_t cur_dts:
int last_IP_duration:
int64_t last_IP_pts:
AVIndexEntry * index_entries:
int nb_index_entries:
unsigned int index_entries_allocated_size:
int64_t nb_frames: количество кадров в этом потоке (если известно) или 0
int64_t pts_buffer [MAX_REORDER_DELAY+1]:

ByteIOContext

Структура, которая хранит низкоуровневую информацию о файле фильма.

unsigned char * buffer:
int buffer_size:
unsigned char * buf_ptr:
unsigned char * buf_end:
void * opaque:
int(* read_packet )(void *opaque, uint8_t *buf, int buf_size):
int(* write_packet )(void *opaque, uint8_t *buf, int buf_size):
offset_t(* seek )(void *opaque, offset_t offset, int whence):
offset_t pos:
int must_flush:
int eof_reached:
int write_flag:
int is_streamed:
int max_packet_size:
unsigned long checksum:
unsigned char * checksum_ptr:
unsigned long(* update_checksum )(unsigned long checksum:
const uint8_t *buf, unsigned int size)
:
int error: содержит код ошибки или 0, если ошибки не произошло.

SDL_AudioSpec

Используется для описания формата некоторых аудиоданных.

freq: частота звука в сэмплах в секунду.
format: аудиоформат данных.
channels: количество каналов: 1 — моно, 2 — стерео, 4 объёмных, 6 объёмных с центровкой и LFE
silence: значение молчания звукового буфера (рассчитывается).
samples: размер аудиобуфера в сэмплах.
size: Размер аудиобуфера в байтах (рассчитывается).
callback(..): функция обратного вызова для заполнения аудиобуфера.
userdata: указатель пользовательских данных, которые передаются в функцию обратного вызова.

Допустимы следующие значения формата:

AUDIO_U8 — 8-битные сэмплы без знака.
AUDIO_S8 — подписанные 8-битные сэмплы.
AUDIO_U16 или AUDIO_U16LSB — не поддерживается всеми аппаратными средствами (беззнаковый 16-разрядный младший порядок байтов).
AUDIO_S16 или AUDIO_S16LS — не поддерживается всеми аппаратными средствами (16-разрядный со старым порядком байтов)
AUDIO_U16MSB — не поддерживается всеми аппаратными средствами (беззнаковый 16-разрядный big-endian).
AUDIO_S16MS — не поддерживается всеми аппаратными средствами (16-разрядный со старшим порядком байтов).
AUDIO_U16SYS: либо AUDIO_U16LSB, либо AUDIO_U16MSB — в зависимости от аппаратного процессора.
AUDIO_S16SYS: либо AUDIO_S16LSB, либо AUDIO_S16MSB — в зависимости от аппаратного процессора.

SDL_Event

Основная структура для событий.

type: тип события.
active: событие активации (см. SDL_ActiveEvent).
key: событие клавиатуры (см. SDL_KeyboardEvent).
motion: событие движения мыши (см. SDL_MouseMotionEvent).
button: событие нажатия на кнопку мыши (см. SDL_MouseButtonEvent).
jaxis: событие движения оси джойстика (см. SDL_JoyAxisEvent).
jball: событие движения трекбола джойстика (см. SDL_JoyBallEvent).
jhat: событие движения «шапки» джойстика (см. SDL_JoyHatEvent).
jbutton: событие нажатия на кнопку джойстика (см. SDL_JoyButtonEvent).
resize: событие изменения размера окна приложения (см. SDL_ResizeEvent).
expose: событие раскрытия окна приложения (см. SDL_ExposeEvent).
quit: событие запроса на выход из приложения (см. SDL_QuitEvent).
user: пользовательское событие (см. SDL_UserEvent).
syswm: неопределенное событие диспетчера окон (см. SDL_SysWMEvent).

Вот типы событий. См. документацию SDL для получения дополнительной информации:

SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN/UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN/UP SDL_MouseButtonEvent
SDL_JOYAXISMOTION SDL_JoyAxisEvent
SDL_JOYBALLMOTION SDL_JoyBallEvent
SDL_JOYHATMOTION SDL_JoyHatEvent
SDL_JOYBUTTONDOWN/UP SDL_JoyButtonEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_QUIT SDL_QuitEvent
SDL_USEREVENT SDL_UserEvent
SDL_SYSWMEVENT SDL_SysWMEvent

SDL_Overlay

YUV-наложение.

format: формат наложения (см. ниже).
w, h: Ширина/высота наложения.
planes: количество планов в наложении. Обычно либо 1, либо 3.
pitches: массив отступов, по одному на каждый план. Отступ — это длина строки в байтах.
pixels: массив указателей на данные для каждого плана. Наложение должно быть заблокировано перед использованием этих указателей.
hw_overlay: установливается как равное 1, если наложение аппаратно ускорено.

SDL_Rect

Прямоугольная область.

Sint16 x, y: положение верхнего левого угла прямоугольника.
Uint16 w, h: ширина и высота прямоугольника.

SDL_Rect определяет прямоугольную область пикселей. Он используется SDL_BlitSurface для определения областей блитинга и некоторыми другими функциями видео.

SDL_Surface

Графическая структура внешней стороны (поверхности).

Uint32 flags: Флаги внешней стотроны. Только для чтения.
SDL_PixelFormat *format: только для чтения.
int w, h: ширина и высота. Только для чтения.
Uint16 pitch: шаг. Только для чтения.
void *pixels: указатель на фактические данные пикселей. Только для записи.
SDL_Rect clip_rect: прямоугольная внешняя сторона клипа. Только для чтения.
int refcount: используется для выделения памяти. Преимущественно, для чтения.
Эта структура также содержит приватные поля, не показанные здесь.

SDL_Surface представляет область «графической» памяти, которая может быть нарисована. Кадр буфера видео возвращается как SDL_Surface с помощью SDL_SetVideoMode и SDL_GetVideoSurface. Поля w и h являются значениями, представляющими ширину и высоту поверхности в пикселях. Поле пикселей является указателем на фактические данные пикселей. Примечание: поверхность должна быть заблокирована (через SDL_LockSurface) перед доступом к этому полю. Поле clip_rect является отсеченным прямоугольником, установленным SDL_SetClipRect.

Поле флагов поддерживает следующие OR-значения:

SDL_SWSURFACE — внешняя сторона хранится в системной памяти.
SDL_HWSURFACE — внешняя сторона хранится в видеопамяти.
SDL_ASYNCBLIT — внещняя сторона использует асинхронные блики, если это возможно.
SDL_ANYFORMAT — допускается любой пиксельный формат (поверхность дисплея).
SDL_HWPALETTE — поверхность имеет эксклюзивную палитру.
SDL_DOUBLEBUF — поверхность с двойной буферизацией (поверхность дисплея).
SDL_FULLSCREEN — полноэкранная поверхность (поверхность дисплея).
SDL_OPENGL — поверхность имеет контекст OpenGL (поверхность дисплея).
SDL_OPENGLBLIT — поверхность поддерживает блинтинг OpenGL (поверхность дисплея). ПРИМЕЧАНИЕ. Этот параметр предназначен только для совместимости и не рекомендуется для нового кода.
SDL_RESIZABLE — для поверхности возможно изменение размеров (поверхность дисплея).
SDL_HWACCEL — поверхностный блит использует аппаратное ускорение.
SDL_SRCCOLORKEY — поверхностность использует цветовой блиттинг.
SDL_RLEACCEL — цветовой блиттинг ускоряется с помощью RLE.
SDL_SRCALPHA — поверхностный блит использует альфа-смешение.
SDL_PREALLOC — поверхность использует предварительно выделенную память.

SDL_Thread

Эта структура не зависит от системы, и вам, вероятно, не нужно её использовать. Для получения дополнительной информации см. src/thread/sdl_thread_c.h в исходном коде.

SDL_cond

Эта структура не зависит от системы, и вам, вероятно, не нужно её использовать. Для получения дополнительной информации см. src/thread/<system>/SDL_syscond.c в исходном коде.

SDL_mutex

Эта структура не зависит от системы, и вам, вероятно, не нужно её использовать. Для получения дополнительной информации см. src/thread/<system>/SDL_sysmutex.c в исходном коде.






Ссылки


An FFmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines

FFmpeg, SDL

FFmpeg HomePage

SDL HomePage







Читайте также в блоге компании EDISON:


Руководство по FFmpeg libav
Edison
Изобретаем успех: софт и стартапы

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое