[Parte 2/2] Guía de FFmpeg y SDL o Cómo escribir un reproductor de video en menos de 1000 líneas


Publicamos el resto de la traducción al ruso del manual, que está algo desactualizado, pero no ha perdido su relevancia, ya que este tutorial ayuda a comprender la "cocina" de la creación de aplicaciones de video utilizando las bibliotecas FFmpeg y SDL.

Y aunque lo intentamos, las dificultades de traducción son inevitables en un texto tan voluminoso . Informar errores (preferiblemente en mensajes privados): juntos lo haremos mejor.

Tabla de contenido


Software EDISON - desarrollo web
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;
}


Ahora que tenemos un reproductor más o menos decente en el que incluso puedes ver una película, vamos a llegar a fin de mes. La última vez, tocamos ligeramente la sincronización, es decir, la sincronización de sonido con video, en ese orden, no al revés. Vamos a hacer que este sea el mismo diseño que con el video: haga un reloj de video interno para rastrear qué tan lejos está el flujo de video y sincronizar el audio con él. Más adelante generalizaremos aún más: sincronizamos audio y video con un reloj externo.

Implementación de reloj de video


Ahora queremos hacer un reloj de video similar al reloj de audio que teníamos la última vez: un valor interno que devuelve el desplazamiento de tiempo actual del video que se está reproduciendo actualmente. Puede pensar que será tan simple como actualizar el temporizador con el PTS actual del último cuadro mostrado. Sin embargo, no olvide que el tiempo entre cuadros de video puede ser demasiado largo si caemos al nivel de milisegundos. Por lo tanto, la solución es rastrear otro valor, el tiempo en el que configuramos el reloj de video en el PTS del último cuadro. Por lo tanto, el valor actual del reloj de video será PTS_of_last_frame + ( current_time - time_elapsed_since_PTS_value_was_set) Esta solución es muy similar a lo que hicimos con get_audio_clock .

Entonces, en nuestra estructura completa, vamos a poner doble video_current_pts e int64_t video_current_pts_time . El reloj se actualizará en la función 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();

No olvides inicializarlo en stream_component_open :

is->video_current_pts_time = av_gettime();

Y ahora todo lo que necesitamos es alguna forma de obtener la información:

double get_video_clock(VideoState *is) {
  double delta;

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

Resumen del reloj


Pero, ¿por qué obligarse a usar un reloj de video? Puede ir más allá y cambiar nuestro código de sincronización de video para que el audio y el video no intenten sincronizarse entre sí. Imagina lo complicado que será si intentamos hacer esto con una opción de línea de comando, como en FFplay. Entonces, hagamos un resumen: crearemos una nueva función de contenedor, get_master_clock , que verifica la variable av_sync_type , y luego llama a get_audio_clock , get_video_clock o cualquier otro reloj que pueda usar. Incluso podemos usar un reloj de computadora, que llamamos 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;
...
}

Sincronización de audio


Ahora la parte más difícil: sincronizar audio con el reloj de video. Nuestra estrategia es medir dónde está el audio, compararlo con el reloj de video y luego averiguar cuántas muestras necesitamos ajustar, es decir, ¿necesitamos acelerar al soltar las muestras o reducir la velocidad agregando?

Ejecutamos la función synchronize_audio cada vez que procesamos cada conjunto de muestras de audio que obtenemos para reducir o aumentar adecuadamente este conjunto. Sin embargo, no queremos sincronizar todo el tiempo, porque el procesamiento de audio ocurre con mucha más frecuencia que el procesamiento de paquetes de video. Entonces, vamos a establecer el número mínimo de llamadas consecutivas a la función synchronize_audioque se consideran no sincronizados antes de molestarnos en hacer algo. Por supuesto, como la última vez, "desincronización" significa que el reloj de audio y el reloj de video difieren en una cantidad mayor que el umbral de sincronización.

Entonces vamos a usar un coeficiente fraccionario, digamos, s , y ahora, digamos que tenemos Nconjuntos de muestras de audio que no estaban sincronizadas. El número de muestras que no sincronizamos también puede variar mucho, por lo que tomamos el valor promedio de cuánto no está sincronizado cada uno de ellos. Por ejemplo, la primera llamada podría mostrar que no estamos sincronizados durante 40 ms, la siguiente durante 50 ms y así sucesivamente. Pero no vamos a tomar un significado simple, porque los valores más recientes son más importantes que los anteriores. Entonces, vamos a usar un coeficiente fraccionario, digamos, c , y resumir las diferencias de la siguiente manera: diff_sum = new_diff + diff_sum * c . Cuando estamos listos para encontrar la diferencia promedio, simplemente calculamos avg_diff =diff_sum * (1 - c ).

¿Qué diablos está pasando aquí? La ecuación parece una especie de magia. Bueno, esto es básicamente un promedio ponderado usando una serie geométrica como pesos. No sé si hay un nombre para esto (¡incluso lo revisé en Wikipedia!), Pero para obtener más información, aquí hay una explicación (o aquí: weightedmean.txt ).

Así es como se ve nuestra función:

/* 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;
}

Entonces todo está bien con nosotros; sabemos aproximadamente cuánto el sonido no es consistente con el video o con lo que usamos como reloj. Entonces, ahora calculemos cuántas muestras necesitamos agregar o descartar colocando este código en la sección "Reducción / expansión del código de búfer":

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

Recuerde que audio_length * ( sample_rate * # of channel * 2) es el número de muestras en audio_length segundos de audio. Por lo tanto, el número de muestras que queremos será igual al número de muestras que ya tenemos, más o menos el número de muestras que corresponden a la cantidad de tiempo durante el cual se reprodujo el sonido. También estableceremos un límite sobre cuán grande o pequeña puede ser nuestra corrección, porque si cambiamos demasiado nuestro búfer, será demasiado molesto para el usuario.

Corrección del número de muestras.


Ahora tenemos que arreglar el sonido. Es posible que haya notado que nuestra función synchronize_audio devuelve un tamaño de muestra, que luego nos dice cuántos bytes enviar a la secuencia. Entonces solo necesitamos ajustar el tamaño de la muestra al valor deseado. Esto funciona para reducir el tamaño de la muestra. Pero si necesita aumentarlo, no podemos aumentar el tamaño de la muestra, ¡porque no hay más datos en el búfer! Por lo tanto, debemos agregar un poco. ¿Pero qué agregar exactamente? Sería una tontería tratar de extrapolar el audio, así que usemos el audio que ya tenemos, agregando el valor de la última muestra al búfer.

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

Ahora estamos devolviendo el tamaño de la muestra, y hemos terminado con esta función. Todo lo que necesitamos hacer ahora es usar esto:

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;

Todo lo que hicimos fue insertar una llamada syncize_audio . (Además, asegúrese de verificar el código fuente, en el que inicializamos las variables que no me molesté en definir).

Y la última, antes de terminar: necesitamos agregar la condición "si" para asegurarnos de que no sincronizamos el video si es el reloj principal:

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

¡Y funciona! Asegúrese de verificar el archivo fuente para inicializar las variables que no me molesté en definir o inicializar. Luego compila:

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

Y el vuelo será normal.

En la última lección rebobinaremos.






Lección 7: Buscar


Listado completo 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;

}

Procesamiento de comandos de búsqueda


Ahora vamos a agregar algunas capacidades de búsqueda en nuestro reproductor, porque es realmente molesto cuando no se puede rebobinar la película. Además, veremos qué fácil es usar la función av_seek_frame .

Vamos a hacer que las flechas en el teclado "izquierda" y "derecha" desplace la película hacia adelante y hacia atrás un poco, y las flechas "arriba" y "abajo" ya son más significativas. “Un poco”: serán 10 segundos y “mucho”, todos 60. Por lo tanto, necesitamos configurar nuestro bucle principal para que intercepte los eventos de pulsación de teclas. Pero el hecho es que cuando recibimos una pulsación de tecla, no podemos llamar a av_seek_frame directamente. Esto debe hacerse en nuestro bucle principal de decodificación, decode_thread loop. Por lo tanto, en su lugar, agregaremos algunos valores a la estructura principal, que contendrá una nueva posición para la búsqueda y algunos indicadores de búsqueda:

  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

Ahora tenemos que configurar nuestro bucle principal que captura las pulsaciones de teclas:

  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;

Para capturar una tecla, primero miramos si ocurrió el evento SDL_KEYDOWN . Luego verificamos qué clave se recibe usando event.key.keysym.sym . Tan pronto como descubramos en qué dirección miramos, calculamos una nueva hora, agregando un incremento al valor de nuestra nueva función get_master_clock . Luego llamamos a la función stream_seek para establecer los valores de seek_pos , etc. Convierta nuestro nuevo tiempo en unidades de marca de tiempo internas avcodec . Recuerde que las marcas de tiempo en las transmisiones se miden en cuadros, no en segundos, utilizando la siguiente fórmula: segundos = cuadros * time_base ( fps ).Por defecto, avcodec está configurado en 1,000,000 de cuadros por segundo (por lo que una posición de 2 segundos tendrá una marca de tiempo de 2,000,000). ¿Por qué necesitamos convertir este valor? Ver más adelante.

Aquí está nuestra función stream_seek . Tenga en cuenta que establecemos la bandera si volvemos:

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

Ahora pasemos a nuestro hilo de decodificación , donde realmente realizamos una búsqueda. En los archivos de origen puede ver que hemos marcado el área "la búsqueda está en curso". Bueno, lo vamos a poner allí ahora mismo.

La búsqueda se centra en la función av_seek_frame . Esta función toma como argumento el formato de contexto, secuencia, marca de tiempo y conjunto de indicadores. La función buscará la marca de tiempo que le dé. La unidad de marca de tiempo es la base de tiempo de la secuencia que pasa a la función. Sin embargo, no necesita pasarlo a la secuencia (indicado al pasar el valor -1). Si hace esto, time_base estará en la unidad de tiempo interna avcodeco 1000000 fps. Es por eso que multiplicamos nuestra posición por AV_TIME_BASE cuando configuramos seek_pos .

Sin embargo, a veces puede (raramente) tener problemas para algunos archivos si pasa av_seek_frame - 1 para la secuencia, por lo que vamos a seleccionar la primera secuencia en nuestro archivo y pasarla a av_seek_frame . No olvide que debemos cambiar la escala de nuestra marca de tiempo para estar en el nuevo "sistema de coordenadas".

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 ) es una función que escala la marca de tiempo de una base a otra. Básicamente, calcula a * b / c , pero esta función es útil porque este cálculo a veces conduce al desbordamiento. AV_TIME_BASE_Q es una versión fraccional de AV_TIME_BASE . Son completamente diferentes: AV_TIME_BASE * time_in_seconds = avcodec_timestamp y AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (pero tenga en cuenta que AV_TIME_BASE_Qes en realidad un objeto AVRational , por lo que debe usar funciones especiales q en avcodec para procesarlo).

Limpieza de tampones


Por lo tanto, configuramos nuestras búsquedas correctamente, pero aún no hemos terminado. Recuerde, ¿tenemos una cola configurada para acumular paquetes? Ahora que estamos en una marca de tiempo diferente, necesitamos borrar esta cola, de lo contrario, la búsqueda en la película no funcionará. Además, avcodec tiene sus propios buffers internos, que también deben vaciarse para cada flujo.

Para hacer esto, primero debe escribir una función que borre nuestra cola de paquetes. Luego debe indicar de alguna manera a la transmisión de audio y video que borraron los búferes internos avcodec . Podemos hacer esto colocando un paquete especial en la cola después de que se limpia, y cuando (hilos) descubren este paquete especial, simplemente borrarán sus memorias intermedias.

Comencemos con la función de reinicio. Es realmente bastante simple, así que solo te mostraré el código:

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

Ahora que la cola está despejada, agregue nuestro "paquete de limpieza". Pero primero, sería bueno definir qué es y crearlo:

AVPacket flush_pkt;

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

Ahora ponga este paquete en la cola:

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

(Este fragmento de código continúa con el fragmento de código anterior para decode_thread ). También necesitamos modificar packet_queue_put para que no dupliquemos un paquete especial para la limpieza:

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

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

Y luego, en la transmisión de audio y video, ponemos esta llamada en avcodec_flush_buffers justo después de 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;
    }

El fragmento de código anterior es exactamente el mismo para la transmisión de video, con el reemplazo de "audio" por "video".

¡Eso es todo! ¡Lo hicimos! Compila tu jugador:

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

y disfruta de tu reproductor de películas hecho en menos de 1000 líneas de C!

Aunque, por supuesto, hay muchas cosas que se pueden agregar o mejorar.






Epílogo


Entonces, tenemos un jugador que funciona, pero por supuesto no es tan bueno como podría ser. Sería posible modificar el archivo y agregar muchas cosas útiles:

  • Seamos realistas, este jugador apesta. La versión de ffplay.c en la que se basa está completamente desactualizada y, como resultado, este tutorial debe revisarse a fondo. Si desea pasar a proyectos más serios utilizando las bibliotecas FFmpeg, le recomiendo que consulte la última versión de ffplay.c como la próxima tarea.
  • El manejo de errores en nuestro código es terrible y se puede implementar mucho mejor.
  • , , , . , paused , , . , , . av_read_play. - , . , , . : , ffplay.c.
  • .
  • . , , , , VOB-.
  • . , .
  • . .
  • , , , , YUV, time_base.
  • .
  • --, ; ffplay.c .


Si quieres saber más sobre FFmpeg, aquí hemos considerado lejos de todo. El siguiente paso es estudiar la codificación multimedia. Es mejor comenzar con el archivo output_example.c , que encontrará en la distribución FFmpeg. Podría escribir otro libro de texto sobre este tema, pero es poco probable que supere esta guía.

UPDHace mucho tiempo no actualicé este texto, pero mientras tanto el mundo no se detiene. Este tutorial solo requiere actualizaciones simples de API; Muy poco ha cambiado en términos de conceptos básicos. La mayoría de estas actualizaciones han simplificado el código. Sin embargo, aunque recorrí todo el código y lo actualicé, FFplay sigue siendo superior a este reproductor de juguetes. De la mano al corazón, admitimos: en estas lecciones escribimos un reproductor de películas bastante pésimo. Por lo tanto, si hoy (o en el futuro) desea mejorar este tutorial, le recomiendo que se familiarice con FFplay y descubra lo que falta. Creo que esto se refiere principalmente al uso de equipos de video, pero, muy posiblemente, me faltan algunas otras cosas obvias. Tal vez una comparación con el FFplay actual llevaría a una reescritura radical de algunas cosas: aún no lo he visto.

Pero estoy muy orgulloso de que a lo largo de los años mi trabajo haya ayudado mucho, incluso teniendo en cuenta el hecho de que las personas a menudo buscaban códigos en otros lugares. Estoy inmensamente agradecido con Chelyaev , quien asumió la rutina de reemplazar todas las funciones que están desactualizadas desde que escribí esta monografía 8 (!) Años atrás.

Me alegro con la esperanza de que estas lecciones resulten útiles y no aburridas. Si hay alguna sugerencia, error, queja, agradecimiento, etc. con respecto a esta guía, escríbame a dranger dog gmail dot com. Y sí, no tiene sentido pedirme que lo ayude con su proyecto FFmpeg. Hay demasiadas letras similares .






Apéndice 1. Lista de funciones


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

Abre el nombre del archivo multimedia, guarda el contexto de formato en la dirección especificada en ptr .

fmt : si no es NULL, establece el formato del archivo.
buf_size : tamaño del búfer (opcional).
opciones : AVDictionary se rellena con los parámetros de AVFormatContext y demultiplexor.

void avformat_close_input(AVFormatContext **s)

Cierra el archivo multimedia. Sin embargo, no cierra los códecs.

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

Crea un contexto de E / S para usar el recurso especificado en la url .

s : puntero al lugar donde se creará el AVIOContext . En caso de falla, el valor especificado se establece en NULL.
url : nombre del recurso para acceder.
flags : controlan la apertura del recurso especificado en la url .
int_cb : interrumpe la devolución de llamada para uso de nivel de protocolo.
opciones : un diccionario poblado con parámetros de protocolo privado. Cuando la función regrese, el parámetro será destruido y reemplazado con un dict que contiene opciones que no se encontraron. Puede ser nulo.

int av_dup_packet(AVPacket *pkt)

Por supuesto, este es un truco: si este paquete no se ha asignado, lo publicamos aquí. Devuelve 0 en caso de éxito o AVERROR_NOMEM en caso de error.

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

Esta función busca información de flujo no obvia, como la velocidad de fotogramas. Esto es útil para formatos de archivo sin encabezado como MPEG. Se recomienda llamar después de abrir el archivo. Devuelve> = 0 si tiene éxito, AVERROR_ * en caso de error.

AVFrame *avcodec_free_frame()

Nombre antiguo de av_frame_free. Cambiado en lavc 55.28.1.

void av_frame_free (AVFrame **frame)

Libera un marco y cualquier objeto asignado dinámicamente en él, por ejemplo, datos extendidos.

void av_free(void *ptr)

Libera memoria asignada usando av_malloc () o av_realloc (). Puede llamar a esta función con ptr == NULL. Se recomienda que llame a av_freep () en su lugar.

void av_freep(void *ptr)

Libera memoria y establece el puntero en NULL. Utiliza internamente av_free ().

void av_free_packet(AVPacket *pkt)

Envuelva el método de destrucción del paquete (pkt-> destruct).

int64_t av_gettime()

Obtenga la hora actual en microsegundos.

void av_init_packet(AVPacket *pkt)

Inicializando campos opcionales del paquete.

void *av_malloc(unsigned int size)

Tamaño de bytes de asignación de memoria con alineación adecuada para todos los accesos a memoria (incluidos los vectores, si están disponibles en la CPU). av_malloc (0) debería devolver un puntero distinto de cero.

void *av_mallocz(unsigned int size)

Igual que av_malloc (), pero inicializa la memoria a cero.

double av_q2d(AVRational a)

Dobles AVRational.

int av_read_frame(AVFormatContext *s, AVPacket *pkt)

Devuelve el siguiente cuadro de flujo. La información se almacena como un paquete en pkt.

El paquete devuelto es válido hasta el siguiente av_read_frame () o hasta av_close_input_file () y debe liberarse usando av_free_packet. Para un paquete de video contiene exactamente un cuadro. Para audio, contiene un número entero de cuadros si cada cuadro tiene un tamaño fijo conocido (por ejemplo, datos PCM o ADPCM). Si los cuadros de audio son de tamaño variable (por ejemplo, audio MPEG), entonces contiene un cuadro.

pkt-> pts, pkt-> dts y pkt-> duración siempre se establecen en los valores correctos en unidades de AVStream.timebase (y se supone que el formato no puede proporcionarlos). pkt-> pts puede ser AV_NOPTS_VALUE si el formato de video tiene cuadros B, por lo que es mejor confiar en pkt-> dts si no está desempacando la carga útil.

Resultado devuelto: 0, si todo está bien, <0, si hay un error o el final del archivo.

void av_register_all();

Registra todos los códecs en la biblioteca.

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

Devuelve a * bq / cq .

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

Busca un fotograma clave en una marca de tiempo.

stream_index : si stream_index es -1, se selecciona la secuencia predeterminada y la marca de tiempo se convierte automáticamente de unidades AV_TIME_BASE a una base de tiempo específica de la secuencia.
marca de tiempo : marca de tiempo medida en unidades de AVStream.time_base o, si no se especifica ninguna secuencia, entonces en unidades de AV_TIME_BASE.
flags : establece los parámetros relacionados con la dirección y el modo de búsqueda:
AVSEEK_FLAG_ANY: busca en cualquier cuadro, no solo en los clave.
AVSEEK_FLAG_BACKWARD: busca en la dirección opuesta.
AVSEEK_FLAG_BYTE: Búsqueda basada en la posición en bytes.

AVFrame *avcodec_alloc_frame()

Nombre antiguo de av_frame_alloc. Cambiado en lavc 55.28.1.

AVFrame *av_frame_alloc()

Selecciona un AVFrame y lo inicializa. Puede liberarse usando av_frame_free ().

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

Decodifica un cuadro de audio de avpkt a cuadro. La función avcodec_decode_audio4 () decodifica un archivo de audio de AVPacket. Para su decodificación, se usa un códec de audio, que se asoció con avctx usando avcodec_open2 (). El cuadro decodificado resultante se almacena en el AVFrame especificado. Si el marco se ha desempaquetado, establecerá got_frame_ptr en 1.

Advertencia: el búfer de entrada, avpkt-> data, debe ser FF_INPUT_BUFFER_PADDING_SIZE mayor que los bytes de lectura reales, porque algunos lectores optimizados de flujo de bits leen 32 o 64 bits a la vez y pueden leer hasta el fin.

avctx : contexto de códec.
marco : marco objetivo.
got_frame_ptr : target int, que se establecerá si el marco fue desempaquetado.
AVPKT: AVPacket que contiene audio.

Resultado devuelto: si se devuelve un error, se devuelve un valor negativo; de lo contrario, se devuelve el número de bytes utilizados desde el AVPacket de entrada.

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

Decodifica un cuadro de video de buf a una imagen. La función avcodec_decode_video2 () decodifica un cuadro de video desde un búfer de entrada de tamaño buf_size. Para su decodificación, se usa un códec de video, que se asoció con avctx usando avcodec_open2 (). El cuadro decodificado resultante se guarda en la imagen.

Advertencia: los ejemplos de alineación y los problemas de búfer que se aplican a avcodec_decode_audio4 también se aplican a esta función.

avctx : contexto de códec.
picture : AVFrame en el que se guardará el video decodificado.
frameFinished : cero si no se pueden desempaquetar cuadros, de lo contrario no es igual a cero.
avpkt: entrada AVPacket que contiene el búfer de entrada. Puede crear dicho paquete usando av_init_packet (), luego, habiendo especificado los datos y el tamaño, algunos decodificadores pueden necesitar adicionalmente otros campos, como banderas y AV_PKT_FLAG_KEY. Todos los decodificadores están diseñados para usar la menor cantidad de campos posible.

Resultado devuelto: en caso de error, se devuelve un valor negativo; de lo contrario, se utiliza el número de bytes o cero si no se pueden descomprimir tramas.

int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)

Un método de acceso simple para obtener best_effort_timestamp de un objeto AVFrame.

AVCodec *avcodec_find_decoder(enum CodecID id)

Busca un decodificador con CodecID. Devuelve NULL en caso de error. Debe llamarse después de obtener el AVCodecContext requerido de la secuencia en AVFormatContext usando codecCtx-> codec_id.

void avcodec_flush_buffers(AVCodecContetx *avctx)

Tampón de descarga. Se llama al buscar o cambiar a otra transmisión.

AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)

Asigna AVCodecContext y establece sus campos en valores predeterminados.

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

Copie la configuración del origen AVCodecContext en el destino AVCodecContext. El contexto resultante del códec de destino se cerrará, es decir. debe llamar a avcodec_open2 () antes de usar este AVCodecContext para decodificar / codificar datos de video / audio.

dest : debe inicializarse con avcodec_alloc_context3 (NULL), de lo contrario no se inicializará.

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

Inicializa avctx para usar el códec especificado en el códec . Debe usarse después de avcodec_find_decoder. Devuelve cero en caso de éxito y un valor negativo en caso de error.

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

Establece la estructura que los puntos de imagen que, con el PTR búfer , pix_fmt formato , y la anchura y la altura especificada. Devuelve el tamaño de los datos de la imagen en bytes.

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

Calcula cuántos bytes se necesitan para una imagen de un ancho, alto y formato de imagen determinados.

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

Devuelve SwsContext para usar en sws_scale.

srcW , srcH , srcFormat : ancho, alto y formato de los píxeles deseados.
dstW , dstH , dstFormat : ancho, alto y formato de los píxeles finales.
flags : el método de escalado a utilizar.
Las siguientes opciones están disponibles: SWS_FAST_BILINEAR, SWS_BILINEAR, SWS_BICUBIC, SWS_X, SWS_POINT, SWS_AREA, SWS_BICUBLIN, SWS_GAUSS, SWS_SINC, SWS_LANCZOS, SWS_SPLINE.
Otros indicadores incluyen indicadores de capacidad de CPU: SWS_CPU_CAPS_MMX, SWS_CPU_CAPS_MMX2, SWS_CPU_CAPS_3DNOW, SWS_CPU_CAPS_ALTIVEC.
Otros indicadores incluyen (actualmente no totalmente implementado) SWS_FULL_CHR_H_INT, SWS_FULL_CHR_H_INP y SWS_DIRECT_BGR.
Finalmente, hay SWS_ACCURATE_RND y quizás el más útil para principiantes, SWS_PRINT_INFO.
No tengo idea de lo que hacen la mayoría de ellos. ¿Quizás me escribes?
srcFilter , dstFilter : SwsFilter para origen y destino. SwsFilter permite el filtrado de color / brillo. El valor predeterminado es NULL.
param : debe ser un puntero a un búfer int [2] con coeficientes. No documentado Parece que se usa para modificar ligeramente los algoritmos de escala estándar. El valor predeterminado es NULL. ¡Solo para expertos!

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

Escala los datos en src de acuerdo con nuestra configuración en nuestro SwsContext * c .
srcStride y dstStride son los tamaños de fila de origen y destino.

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

Agrega una función de devolución de llamada que se ejecuta después de un número específico de milisegundos. La función de devolución de llamada pasa el intervalo de temporizador actual y el parámetro proporcionado por el usuario de la llamada SDL_AddTimer y devuelve el siguiente intervalo de temporizador. (Si el valor de retorno de la devolución de llamada coincide con el valor pasado, el temporizador continúa funcionando a la misma velocidad). Si el valor de retorno de la devolución de llamada es 0, el temporizador se cancela.
Otra forma de cancelar el temporizador actual es llamar a SDL_RemoveTimer con el identificador de temporizador (que fue devuelto por SDL_AddTimer).

La función de devolución de llamada del temporizador puede ejecutarse en un subproceso diferente que su programa principal y, por lo tanto, no debe llamar a ninguna función desde sí misma. Sin embargo, siempre puede llamar a SDL_PushEvent.

El grado de detalle del temporizador depende de la plataforma, pero debe esperar que sea de al menos 10 ms, ya que este es el valor más común. Esto significa que si solicita un temporizador de 16 ms, la devolución de llamada comenzará después de aproximadamente 20 ms en un sistema descargado. Si necesita configurar un indicador que indique la actualización de cuadros a una velocidad de 30 cuadros por segundo (cada 33 ms), puede configurar un temporizador para 30 ms (consulte el ejemplo a continuación). Si utiliza esta función, debe pasar SDL_INIT_TIMER a SDL_Init.

Devuelve el valor del identificador para el temporizador agregado o NULL si se produce un error.

Formato para devolución de llamada:
Uint32 callback ( Uint32, void * param)


int SDL_CondSignal(SDL_cond *cond)

Reiniciando uno de los hilos esperando la variable de condición cond . Devuelve 0 en caso de éxito y -1 en caso de error.

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

Desbloquee el mutex proporcionado y espere a que otro hilo llame a SDL_CondSignal o SDL_CondBroadcast para la variable de condición cond, luego vuelva a bloquear el mutex. El mutex debe estar bloqueado antes de ingresar a esta función. Devuelve 0 cuando se recibe una señal, o -1 en caso de error.

SDL_cond *SDL_CreateCond(void);

Crea una variable de condición.

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

SDL_CreateThread crea un nuevo hilo de ejecución que comparte toda la memoria global de su padre, manejadores de señal, descriptores de archivo, etc. Y ejecuta la función fn , pasándole los datos del puntero vacío. El hilo termina cuando fn devuelve un valor.

void SDL_Delay (Uint32 );

Espera el número especificado de milisegundos. SDL_Delay esperará al menos el tiempo especificado, pero posiblemente más tiempo debido a la planificación del sistema operativo.
Nota: espere una granularidad de retraso de al menos 10 ms. Algunas plataformas tienen medidas más cortas, pero esta es la opción más común.

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

SDL_CreateYUVOverlay crea una superposición YUV del ancho, alto y formato especificados (para obtener una lista de formatos disponibles, consulte la estructura de datos SDL_Overlay) para la pantalla proporcionada. Devuelve SDL_Overlay.

display debería ser una superficie derivada de SDL_SetVideoMode, de lo contrario esta función funcionará de manera predeterminada.

El término "superposición" es incorrecto, porque si la superposición no se crea en el hardware, el contenido de la superficie de la pantalla debajo del área donde se muestra la superposición se sobrescribirá cuando se muestre la superposición.

int SDL_LockYUVOverlay(SDL_Overlay *overlay)

SDL_LockYUVOverlay bloquea la superposición para el acceso directo a los datos de píxeles. Devuelve 0 en caso de éxito o -1 en caso de error.

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)

Desbloquea una superposición previamente bloqueada. La superposición debe estar desbloqueada antes de poder mostrarse.

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)

Coloca la superposición en la superficie especificada cuando se creó. La estructura dstrect SDL_Rect define la posición y el tamaño del destino. Si dstrect tiene más o menos superposición, entonces la superposición se escalará, esto está optimizado para una escala 2x. Devuelve 0 si tiene éxito.

void SDL_FreeYUVOverlay(SDL_Overlay *overlay)

Libera la superposición creada por SDL_CreateYUVOverlay.

int SDL_Init(Uint32 flags);

Inicializa el SDL. Esto debería llamarse antes que todas las demás funciones SDL. El parámetro flags especifica qué partes del SDL se inicializarán.

SDL_INIT_TIMER: inicializa el subsistema del temporizador.
SDL_INIT_AUDIO: inicializa el subsistema de audio.
SDL_INIT_VIDEO: inicializa el subsistema de video.
SDL_INIT_CDROM: inicializa el subsistema de CD-ROM.
SDL_INIT_JOYSTICK: inicializa el subsistema joystick.
SDL_INIT_EVERYTHING: inicializa todo lo anterior.
SDL_INIT_NOPARACHUTE: no permite que SDL detecte errores fatales.
SDL_INIT_EVENTTHREAD: inicia el administrador de eventos en un hilo separado.

Devuelve -1 en caso de error o 0 en caso de éxito. Puede obtener un mensaje de error extendido llamando a SDL_GetError. Una causa típica de un error es el uso de una pantalla específica sin el soporte correspondiente para el subsistema, por ejemplo, la ausencia de un controlador de mouse cuando se utiliza un búfer de trama con el dispositivo. En este caso, puede compilar SDL sin un mouse o configurar la variable de entorno "SDL_NOMOUSE = 1" antes de iniciar la aplicación.

SDL_mutex *SDL_CreateMutex(void);

Crea un nuevo mutex desbloqueado.

int SDL_LockMutex(SDL_mutex *mutex)

SDL_LockMutex es un alias para SDL_mutexP. Bloquea un mutex que se creó previamente con SDL_CreateMutex. Si el mutex ya está bloqueado por otro subproceso, entonces SDL_mutexP no devuelve un valor hasta que el subproceso bloqueado por él lo desbloquee (usando SDL_mutexV). Cuando se vuelve a llamar al mutex, se debe llamar a SDL_mutexV (también conocido como SDL_UnlockMutex) el mismo número de veces para devolver el mutex a un estado desbloqueado. Devuelve 0 en caso de éxito o -1 en caso de error.

int SDL_UnlockMutex(SDL_Mutex *mutex)

Desbloqueo Mutex.

int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

Esta función abre la unidad de audio con los parámetros requeridos y devuelve 0 si tiene éxito, colocando los parámetros de hardware reales en la estructura a la que apunta en última instancia. Si se recibe un valor NULL, se garantizará que los datos de audio pasados ​​a la función de devolución de llamada tengan el formato requerido y, si es necesario, se convertirán automáticamente al formato de audio de hardware. Esta función devuelve -1 si no se pudo abrir el dispositivo de audio o no se pudo configurar la transmisión de audio.

Para abrir un dispositivo de audio, debe crear la SDL_AudioSpec deseada. Luego debe completar esta estructura con las especificaciones de audio deseadas.

deseado-> frecuencia: frecuencia de sonido deseada en muestras por segundo.
deseado-> formato : formato de audio deseado (ver SDL_AudioSpec).
deseado-> canales: Canales necesarios (1 para mono, 2 para estéreo, 4 para sonido envolvente, 6 para sonido envolvente con centrado y LFE).
deseado-> muestras : tamaño deseado del búfer de audio en muestras. Este número debe ser una potencia de dos y el controlador de audio puede ajustarlo a un valor más adecuado para el hardware. Los valores óptimos varían de 512 a 8192 inclusive, dependiendo de la aplicación y la velocidad del procesador. Los valores más pequeños dan como resultado tiempos de respuesta más rápidos, pero pueden conducir a un rendimiento deficiente si la aplicación realiza un procesamiento pesado y no puede llenar el búfer de audio a tiempo. La muestra estéreo consta de los canales derecho e izquierdo en orden LR. Tenga en cuenta que el número de muestras está directamente relacionado con el tiempo utilizando la siguiente fórmula: ms = (muestras * 1000) / frec .
deseado-> devolución de llamada : debe establecerse en una función que se llamará cuando la unidad de audio esté lista para recibir datos adicionales. Se transmiten el puntero al búfer de audio y la longitud en bytes del búfer de audio. Esta función generalmente se ejecuta en un hilo separado y, por lo tanto, es necesario proteger las estructuras de datos a las que accede llamando a SDL_LockAudio y SDL_UnlockAudio en el código. El prototipo de devolución de llamada es devolución de llamada nula ( void * userdata , Uint8 * stream , int len ) . userdata : un puntero almacenado en el campo userdata SDL_AudioSpec. corrienteEs un puntero al búfer de audio que desea llenar con información, y len es la longitud del búfer de audio en bytes.
required-> userdata : este puntero se pasa como el primer parámetro a la función de devolución de llamada.

SDL_OpenAudio lee estos campos de la estructura SDL_AudioSpec deseada pasada a la función e intenta encontrar la configuración de audio que coincida con su deseo. Como se mencionó anteriormente, si el parámetro resultante es NULL, SDL se convierte de la configuración de sonido deseada a la configuración del equipo durante la reproducción.

Si se devuelve NULL, la SDL_AudioSpec requerida es su especificación de trabajo; de lo contrario, la SDL_AudioSpec resultante se convierte en una especificación de trabajo y la especificación deseada se puede eliminar. Los datos en la especificación de trabajo se utilizan al construir SDL_AudioCVT para convertir los datos descargados al formato del equipo.

SDL_OpenAudio calcula los campos de tamaño y silencio para la especificación deseada y la resultante. El campo de tamaño almacena el tamaño total del búfer de audio en bytes, mientras que el silencio almacena el valor utilizado para representar el silencio en el búfer de audio.

El dispositivo de sonido comienza a reproducir silencio cuando está abierto y se debe encender para reproducir llamando a SDL_PauseAudio (0) cuando esté listo para llamar a la función de devolución de llamada de audio. Dado que el controlador de audio puede cambiar el tamaño solicitado del búfer de audio, debe seleccionar cualquier búfer de mezclador local después de abrir el dispositivo de audio.

void SDL_PauseAudio(int pause_on)

Esta función detiene y detiene el procesamiento de devolución de llamada de audio. Debe llamarse con pause_on = 0 después de abrir el dispositivo de audio para comenzar a reproducir el sonido. Esto le permite inicializar de forma segura los datos para la función de devolución de llamada después de abrir la unidad de audio. El silencio se grabará en el dispositivo de audio durante una pausa.

int SDL_PushEvent(SDL_Event *event)

Una cola de eventos que se usa realmente como un canal de comunicación bidireccional. No solo los eventos se pueden leer desde la cola, sino que el usuario también puede poner sus propios eventos en ella. Un evento es un puntero a la estructura del evento que desea poner en cola. El evento se copia en la cola y la persona que llama puede administrar la memoria apuntada después de devolver SDL_PushEvent. Esta función está orientada a subprocesos y se puede llamar de forma segura desde otros subprocesos. Devuelve 0 si tiene éxito o -1 si el evento no se pudo enviar.

int SDL_WaitEvent(SDL_Event *event)

Espera indefinidamente el próximo evento disponible, devolviendo 0 si se produjo un error mientras esperaba eventos, 1 de lo contrario. Si el evento no es NULL, el siguiente evento se elimina de la cola y se almacena en esta área.

void SDL_Quit()

Inhabilita todos los subsistemas SDL y libera los recursos asignados a él. Esto siempre debe llamarse antes de salir.

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

Configuración del modo de video con el ancho, la altura y los bits de píxeles especificados. A partir de SDL 1.2.10, si el ancho y la altura son 0, utilizará el ancho y la altura del modo de video actual (o el modo de escritorio si el modo no está configurado). Si bitsperpixel es 0, se trata como los bits de visualización actuales por píxel. El parámetro flags es el mismo que el campo flags de la estructura SDL_Surface. O una combinación de los siguientes valores:

SDL_SWSURFACE: crea una superficie de video en la memoria del sistema.
SDL_HWSURFACE: crea una superficie de video en la memoria de video.
SDL_ASYNCBLIT: permite el uso de actualizaciones asincrónicas en la superficie de visualización. Esto generalmente ralentiza el trabajo en computadoras de un solo procesador, pero puede aumentar la velocidad en los sistemas SMP.
SDL_ANYFORMAT: generalmente, si una superficie de video con los bits solicitados por píxel (bpp - de bits por píxel) no está disponible, SDL emulará el video con una superficie sombreada. Pasar SDL_ANYFORMAT evita esto y obliga a SDL a usar la superficie del video, independientemente de su profundidad en píxeles.
SDL_HWPALETTE: proporciona acceso exclusivo de SDL a la paleta. Sin este indicador, no siempre puede obtener los colores que solicita con SDL_SetColors o SDL_SetPalette.
SDL_DOUBLEBUF - habilita el doble buffer del hardware; válido solo con SDL_HWSURFACE. Una llamada a SDL_Flip revertirá los búferes y actualizará la pantalla. Todos los dibujos se realizarán en una superficie que no se muestra actualmente. Si no se puede habilitar el almacenamiento en búfer doble, SDL_Flip simplemente ejecutará SDL_UpdateRect a pantalla completa.
SDL_FULLSCREEN SDL: intente utilizar el modo de pantalla completa. Si no es posible cambiar la resolución del hardware (por cualquier motivo), se utilizará la siguiente resolución más alta y la ventana de visualización se centrará en un fondo negro.
SDL_OPENGL: crea un contexto de representación de OpenGL. Se supone que los atributos de video OpenGL con SDL_GL_SetAttribute están preestablecidos.
SDL_OPENGLBLIT: crea un contexto de representación de OpenGL, como se describió anteriormente, pero permite operaciones normales de borrado. Una superficie de pantalla (2D) puede tener un canal alfa, y SDL_UpdateRects se debe utilizar para actualizar los cambios en la superficie de la pantalla. NOTA. Esta opción se guarda solo por compatibilidad y se eliminará en futuras versiones. No recomendado para uso en código nuevo.
SDL_RESIZABL -crea una ventana redimensionable. Cuando el usuario cambia el tamaño de la ventana, se genera el evento SDL_VIDEORESIZE y se puede volver a llamar a SDL_SetVideoMode con un nuevo tamaño.
SDL_NOFRAME Si es posible, SDL_NOFRAME obliga a SDL a crear una ventana sin título o marco. Esta bandera se configura automáticamente en modo de pantalla completa.
Nota. No importa qué indicadores SDL_SetVideoMode puedan satisfacer, se establecen en el elemento de indicadores de la superficie devuelta.
NOTA. Bit pixel 24 usa una representación empaquetada de 3 bytes por pixel. Para el modo más común de 4 bytes por píxel, use píxeles de 32 bits. Curiosamente, tanto 15 como 16 solicitarán un modo de 2 bytes por píxel, pero con diferentes formatos de píxeles.
NOTA. Use SDL_SWSURFACE si planea realizar manipulaciones de píxeles separadas o arrastrar superficies usando canales alfa y requiere una alta velocidad de cuadros. Cuando utiliza superficies de hardware (SDL_HWSURFACE), la SDL copia las superficies de la memoria de video a la memoria del sistema cuando las bloquea y viceversa cuando las desbloquea. Esto puede conducir a una disminución significativa en el rendimiento. (Tenga en cuenta que puede consultar una superficie de hardware pero aún obtener una superficie de software. Muchas plataformas solo pueden proporcionar una superficie de hardware cuando se usa SDL_FULLSCREEN). SDL_HWSURFACE se usa mejor cuando las superficies que parpadeará también se pueden almacenar en la memoria de video.
NOTA. Si desea controlar la posición en la pantalla al crear la superficie de la ventana, puede hacerlo configurando las variables de entorno "SDL_VIDEO_CENTERED = center" o "SDL_VIDEO_WINDOW_POS = x, y". Puede instalarlos a través de SDL_putenv.

Valor de retorno: superficie del búfer de trama o NULL en caso de falla. SDL_Quit libera la superficie devuelta y la persona que llama no debe liberarla.
NOTA. Esta regla incluye llamadas consecutivas a SDL_SetVideoMode (es decir, cambio de tamaño): la superficie existente se liberará automáticamente.






Apéndice 2. Estructuras de datos



AVCodecContext

Toda la información sobre el códec de la transmisión, de AVStream-> códec. Algunos atributos importantes:

AVRational time_base : número de cuadros por segundo
int sample_rate : samples per second
int channel : número de canales

Vea la lista completa (muy impresionante) aquí ( archivo web, ya que el enlace original ya no existe ). Muchos parámetros se utilizan principalmente para codificar, no para decodificar.

AVFormatContext

Campos de datos:

const AVClass * av_class
AVInputFormat * iformat
AVOutputFormat * oformat
void * priv_data :
ByteIOContext pb : utilizado para la manipulación de archivos de bajo nivel.
unsigned int nb_streams : número de subprocesos en el archivo.
AVStream * streams [MAX_STREAMS] : los datos para cada flujo se almacenan aquí.
char filename [1024]: pero ¿qué pasa sin él (en el original - duh ).

Información del archivo:
marca de tiempo int64_t : título de
char [512]:
autor de char [512]: copyright de
char [512]: comentario de char [512]: álbum de char [512]: int año : int pista : género de char [32]: int ctx_flags : Los valores posibles son AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, AVFMT_RAWPICTURE, AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_GENERIC_INDEX AVPacketList * packet_buffer : este búfer solo es necesario cuando los paquetes ya están almacenados pero no decodificados, por ejemplo, para recibir parámetros de códec en secuencias de MPEG. int64_t









start_time : al decodificar: la posición del primer fotograma del componente, en fracciones de segundo, AV_TIME_BASE. NUNCA establezca este valor directamente: se infiere de los valores de AVStream.
int64_t Duration: decodificación : duración de la secuencia, en fracciones de AV_TIME_BASE. NUNCA establezca este valor directamente: se infiere de los valores de AVStream.
int64_t file_size : tamaño total del archivo, 0 si se desconoce.
int bit_rate : decoding : bitrate total de la secuencia en bit / s, 0 si no está disponible. NUNCA lo configure directamente si file_size y la duración conocida en ffmpeg puede calcularlo automáticamente.
AVStream * cur_st
const uint8_t * cur_ptr
int cur_len
AVPacket cur_pkt :
int64_t data_offset :
int index_built : desplazamiento del primer paquete.
int mux_rate :
int packet_size :
int preload :
int max_delay :
int loop_output : número de bucles de salida en formatos compatibles.
int flags :
int loop_input :
unsigned int probesize : decodificación: tamaño de datos de muestra; No se utiliza en la codificación.
int max_analyze_duration : duración máxima en unidades de AV_TIME_BASE durante la cual los datos de entrada deben analizarse en av_find_stream_info ()
const uint8_t * key :
int keylen :

AVIOContext

Contexto de E / S para acceder a los recursos.

const AVClass * av_class : clase para configuraciones privadas.
unsigned char * buffer : inicio del buffer.
int buffer_size : tamaño máximo del búfer.
unsigned char * buf_ptr : posición actual en el búfer.
unsigned char * buf_end : los datos pueden ser más pequeños que buffer + buffer_size si la función de lectura devuelve menos datos de los solicitados, por ejemplo.
void * opaque : puntero privado pasado a read / write / search / ...
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
wherence ) : int64_t pos : position in the file of the current buffer.
int must_flush : verdadero si la próxima búsqueda debe restablecerse.
int eof_reached : verdadero si se alcanza el final del archivo.
int write_flag : verdadero si está abierto para escribir.
int max_packet_size :
suma de verificación larga sin
signo : char sin signo * checksum_ptr :
longitud sin signo (* update_checksum) (suma de verificación larga sin signo, const uint8_t * buf, tamaño int sin signo) :
error int : contiene el código de error o 0 si no se produjo ningún error.
int (* read_pause) (nulo * opaco, int pausa): pausa o reanuda la reproducción para protocolos de transmisión de red, por ejemplo.
int64_t (* read_seek) (vacío * opaco, int stream_index, int64_t timestamp, int flags) : busca la marca de tiempo especificada en la secuencia con el índice stream_index especificado.
int seekable : combinación de los indicadores AVIO_SEEKABLE_ o 0 cuando la secuencia no se puede buscar.
int64_t maxsize : tamaño máximo de archivo utilizado para limitar la selección. Este campo es interno a libavformat y está prohibido el acceso a él desde el exterior.
int direct : avio_read y avio_write deben ejecutarse directamente siempre que sea posible, y no pasar por el búfer, y avio_seek siempre llamará directamente a la función de búsqueda principal.
int64_t bytes_read: estadísticas de lectura de bytes Este campo es interno a libavformat y se deniega el acceso externo.
int seek_count : estadísticas de búsqueda. Este campo es interno a libavformat y está prohibido el acceso a él desde el exterior.
int writeout_count : escribe estadísticas. Este campo es interno a libavformat y está prohibido el acceso a él desde el exterior.
int orig_buffer_size : el tamaño original del búfer utilizado internamente después de verificar y proporcionar un retorno para restablecer el tamaño del búfer. Este campo es interno a libavformat y está prohibido el acceso a él desde el exterior.

AVDictionary

Se usa para pasar parámetros a ffmpeg.

int count :
AVDictionaryEntry * elems :

AVDictionaryEntry

Se usa para almacenar entradas de diccionario en AVDictionary.

char * ket :
char * value :

AVFrame

Esta estructura depende del tipo de códec y, por lo tanto, se determina dinámicamente. Sin embargo, hay propiedades y métodos comunes para esta estructura:

uint8_t * data [4] :
int linesize [4] : información de paso.
uint8_t * base [4] :
int key_frame :
int pict_type :
int64_t pts : estos no son los pts que esperas al decodificar.
int coded_picture_number :
int display_picture_number :
int calidad :
int age :
int referencia :
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 : datos de usuario
uint64_t error [4] :
int type :
int repeat_pict : le indica que repita la imagen un número específico de veces.
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

La estructura en la que se almacenan los datos de paquete sin procesar. Estos datos deben transferirse a avcodec_decode_audio2 o avcodec_decode_video para recibir una trama.

int64_t pts : marca de tiempo de presentación en unidades time_base.
int64_t dts : marca de tiempo de descompresión en unidades de time_base.
uint8_t * data : datos sin procesar.
tamaño int : tamaño de datos.
int stream_index : flujo de donde proviene AVPacket, basado en la cantidad en AVFormatContext.
int flags : PKT_FLAG_KEY se establece si el paquete es un cuadro clave.
int Duration: duración de la presentación en unidades de time_base (0 si no está disponible)
void (* destruct) (struct AVPacket *) : función de liberación de recursos para este paquete (av_destruct_packet por defecto).
void * priv :
int64_t pos : posición de byte en la secuencia, -1 si se desconoce.

AVPacketList

Una simple lista vinculada para paquetes.

AVPacket pkt :
AVPacketList * next :

AVPicture

Esta estructura es exactamente la misma que los dos primeros elementos de datos AVFrame, por lo que a menudo se descarta. Comúnmente utilizado en funciones SWS.

uint8_t * data [4] :
int linesize [4] : el número de bytes en la cadena.

AVRational

Estructura simple para representar números racionales.

int num : numerador.
int den : denominador.

AVStream

La estructura de la corriente. Es probable que use esta información en el códec con mayor frecuencia.

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 :
an enum AVDiscard the date : no es necesario que descarte el paquete; en demultiplexación.
calidad flotante :
int64_t hora_inicio :
int64_t duración:
Char lenguaje [4] :
int need_parsing : 1 -> necesitan análisis completo, 2 -> sólo los encabezados de análisis sintáctico, sin cambio de envase
AVCodecParserContext * parser :
int64_t cur_dts :
int last_IP_duration :
last_IP_pts int64_t :
AVIndexEntry * index_entries :
nb_index_entries int :
un entero sin signo index_entries_allocated_size :
int64_t nb_frames : número de cuadros en esta secuencia (si se conoce) o 0
int64_t pts_buffer [MAX_REORDER_DELAY + 1] :

ByteIOContext

Una estructura que almacena información de bajo nivel sobre un archivo de película.

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
wherence ) : offset_t pos :
int must_flush :
int eof_reached :
int write_flag :
int is_streamed :
int max_packet_size :
suma de verificación larga sin
firmar : unsigned char * checksum_ptr :
unsigned long (* update_checksum) (suma de verificación larga sin firmar:
const uint8_t * buf, tamaño int sin firmar)
:
error int : contiene el código de error o 0 si no se produjo ningún error.

SDL_AudioSpec

Se usa para describir el formato de algunos datos de audio.

freq : frecuencia de sonido en muestras por segundo.
formato : formato de datos de audio.
canales : número de canales: 1 - mono, 2 - estéreo, 4 surround, 6 surround con centrado y
silencio LFE : valor de silencio del búfer de sonido (calculado).
samples : tamaño del búfer de audio en samples.
tamaño : el tamaño del búfer de audio en bytes (calculado).
devolución de llamada (..) : función de devolución de llamada para llenar el búfer de audio.
userdata : un puntero a los datos del usuario que se pasa a la función de devolución de llamada.

Los siguientes valores de formato son

válidos : AUDIO_U8 : muestras sin signo de 8 bits.
AUDIO_S8: muestras firmadas de 8 bits.
AUDIO_U16 o AUDIO_U16LSB: no es compatible con todo el hardware (orden de byte bajo de 16 bits sin signo).
AUDIO_S16 o AUDIO_S16LS: no es compatible con todo el hardware (16 bits con orden de bytes anterior)
AUDIO_U16MSB: no es compatible con todo el hardware (big endian de 16 bits sin signo).
AUDIO_S16MS: no es compatible con todo el hardware (16 bits con alto orden de bytes).
AUDIO_U16SYS: AUDIO_U16LSB o AUDIO_U16MSB, según el procesador de hardware.
AUDIO_S16SYS: AUDIO_S16LSB o AUDIO_S16MSB, dependiendo del procesador de hardware.

SDL_Event

La estructura básica para eventos.

tipo : tipo de evento.
activo : evento de activación (ver SDL_ActiveEvent).
clave : evento de teclado (ver SDL_KeyboardEvent).
movimiento : evento de movimiento del mouse (ver SDL_MouseMotionEvent).
botón : evento de clic del mouse (ver SDL_MouseButtonEvent).
jaxis : evento de movimiento del eje del joystick (ver SDL_JoyAxisEvent).
jball : evento de movimiento del joystick trackball (ver SDL_JoyBallEvent).
jhat : evento de movimiento del encabezado del joystick (consulte SDL_JoyHatEvent).
jbutton : evento de presionar el botón del joystick (ver SDL_JoyButtonEvent).
redimensionar: Evento de cambio de tamaño de la ventana de la aplicación (ver SDL_ResizeEvent).
exponer : evento de ventana abierta de la aplicación (ver SDL_ExposeEvent).
salir : evento de solicitud de salida de la aplicación (ver SDL_QuitEvent).
usuario : evento de usuario (ver SDL_UserEvent).
syswm : evento de administrador de ventanas indefinido (ver SDL_SysWMEvent).

Aquí están los tipos de eventos. Consulte la documentación de SDL para obtener más información:

SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN / the UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN / UP SDL_MouseButtonEvent
SDL_JOYAXISMOTION SDL_Levento
SDL_JUO SDL_ELECO SDJ_LMDLJDJJJJJJJLJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ A A ​​& ©
SDL_JoyHatEvent SDL_JOYHATMOTION
SDL_JOYBUTTONDOWN / the UP SDL_JoyButtonEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_Quit SDL_QuitEvent
SDL_USEREVENT SDLWSSLevevent
SDL_SU_Evevent SDL_UserLeve SDL

SDL_Overlay

Superposición de YUV.

formato : formato de superposición (ver más abajo).
w, h : Ancho / alto de la superposición.
planos : el número de planes para superponer. Por lo general, 1 o 3.
lanzamientos : una serie de sangrías, una para cada plan. La sangría es la longitud de la cadena en bytes.
píxeles : una matriz de punteros de datos para cada plan. La superposición debe estar bloqueada antes de usar estos punteros.
hw_overlay : se establece en 1 si la superposición es acelerada por hardware.

SDL_Rect

El área rectangular.

Sint16 x, y : posición de la esquina superior izquierda del rectángulo.
Uint16 w, h : el ancho y la altura del rectángulo.

SDL_Rect define un área rectangular de píxeles. SDL_BlitSurface lo utiliza para identificar áreas de borrones y algunas otras funciones de video.

SDL_Surface

Estructura gráfica del lado externo (superficie).

Banderas Uint32 : Banderas de los estotrones externos. Solo para leer.
Formato SDL_PixelFormat * : solo lectura.
int w, h : ancho y alto. Solo para leer.
Uint16 pitch : paso. Solo para leer.
vacío * píxeles : un puntero a los datos de píxeles reales. Solo para grabación.
SDL_Rect clip_rect : el rectangular exterior del clip. Solo para leer.
int refcount : usado para asignar memoria. Principalmente para leer.
Esta estructura también contiene campos privados que no se muestran aquí.

SDL_Surface representa un área de memoria "gráfica" que se puede dibujar. El marco del búfer de video se devuelve como SDL_Surface usando SDL_SetVideoMode y SDL_GetVideoSurface. Los campos w y h son valores que representan el ancho y la altura de la superficie en píxeles. El campo de píxeles es un puntero a los datos de píxeles reales. Nota: la superficie debe estar bloqueada (a través de SDL_LockSurface) antes de acceder a este campo. El campo clip_rect es el rectángulo recortado establecido por SDL_SetClipRect.

El campo de

marca admite los siguientes valores OR: SDL_SWSURFACE: el exterior se almacena en la memoria del sistema.
SDL_HWSURFACE: el lado externo se almacena en la memoria de video.
SDL_ASYNCBLIT: el exterior utiliza deslumbramiento asíncrono, si es posible.
SDL_ANYFORMAT: se permite cualquier formato de píxel (superficie de visualización).
SDL_HWPALETTE: la superficie tiene una paleta exclusiva.
SDL_DOUBLEBUF: superficie de doble búfer (superficie de visualización).
SDL_FULLSCREEN: superficie de pantalla completa (superficie de visualización).
SDL_OPENGL: la superficie tiene un contexto OpenGL (superficie de visualización).
SDL_OPENGLBLIT: la superficie admite OpenGL parpadeante (superficie de visualización). NOTA. Esta opción es solo para compatibilidad y no se recomienda para código nuevo.
SDL_RESIZABLE: es posible cambiar el tamaño de una superficie (superficie de visualización).
SDL_HWACCEL - Surface Blit usa aceleración de hardware.
SDL_SRCCOLORKEY - Superficiality utiliza el color blitting.
SDL_RLEACCEL: la aceleración del color se acelera con RLE.
SDL_SRCALPHA: Surface Blyth utiliza una combinación alfa.
SDL_PREALLOC: la superficie utiliza memoria preasignada.

SDL_Thread

Esta estructura es independiente del sistema y probablemente no necesite usarla. Consulte src / thread / sdl_thread_c.h en el código fuente para obtener más información.

SDL_cond

Esta estructura es independiente del sistema y probablemente no necesite usarla. Consulte src / thread / <system> /SDL_syscond.c en el código fuente para obtener más información.

SDL_mutex

Esta estructura es independiente del sistema y probablemente no necesite usarla. Consulte src / thread / <system> /SDL_sysmutex.c en el código fuente para obtener más información.






Enlaces


Un tutorial de FFmpeg y SDL o cómo escribir un reproductor de video en menos de 1000 líneas

FFmpeg , SDL FFmpeg HomePage SDL HomePage











Lea también en el blog de la empresa EDISON:


FFmpeg libav manual

All Articles