[Partie 2/2] Guide de FFmpeg et SDL ou Comment écrire un lecteur vidéo en moins de 1000 lignes


Nous publions le reste de la traduction en russe du manuel, qui est quelque peu dépassé, mais n'a pas perdu de sa pertinence, car ce tutoriel permet de plonger dans la «cuisine» de la création d'applications vidéo à l'aide des bibliothÚques FFmpeg et SDL.

Et bien que nous ayons essayé, les difficultés de traduction sont inévitables dans un texte aussi volumineux . Signalez les bugs (de préférence dans les messages privés) - ensemble, nous ferons mieux.

Table des matiĂšres


Logiciel EDISON - développement 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;
}


Maintenant que nous avons un joueur plus ou moins dĂ©cent sur lequel vous pouvez mĂȘme regarder un film, nous allons maintenant joindre les deux bouts. La derniĂšre fois, nous avons lĂ©gĂšrement abordĂ© la synchronisation, Ă  savoir la synchronisation du son avec la vidĂ©o, dans cet ordre, et non l'inverse. Nous allons faire cela de la mĂȘme disposition qu'avec la vidĂ©o: crĂ©er une horloge vidĂ©o interne pour suivre la distance du flux vidĂ©o et synchroniser l'audio avec. Plus tard, nous gĂ©nĂ©raliserons encore plus - nous synchroniserons l'audio et la vidĂ©o avec une horloge externe.

Implémentation de l'horloge vidéo


Maintenant, nous voulons crĂ©er une horloge vidĂ©o similaire Ă  l'horloge audio que nous avions la derniĂšre fois: une valeur interne qui renvoie le dĂ©calage horaire actuel de la vidĂ©o en cours de lecture. Vous pourriez penser que ce sera aussi simple que de mettre Ă  jour le minuteur avec le PTS actuel de la derniĂšre image affichĂ©e. Cependant, n'oubliez pas que le temps entre les images vidĂ©o peut ĂȘtre trop long si nous passons au niveau de la milliseconde. Par consĂ©quent, la solution consiste Ă  suivre une autre valeur, l'heure Ă  laquelle nous avons rĂ©glĂ© l'horloge vidĂ©o sur le PTS de la derniĂšre image. Ainsi, la valeur actuelle de l' horloge vidĂ©o sera PTS_of_last_frame + ( current_time - time_elapsed_since_PTS_value_was_set) Cette solution est trĂšs similaire Ă  ce que nous avons fait avec get_audio_clock .

Donc, dans notre structure complĂšte, nous allons mettre double video_current_pts et int64_t video_current_pts_time . L'horloge sera mise Ă  jour dans la fonction 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();

N'oubliez pas de l'initialiser dans stream_component_open :

is->video_current_pts_time = av_gettime();

Et maintenant, tout ce dont nous avons besoin est d'un moyen d'obtenir les informations:

double get_video_clock(VideoState *is) {
  double delta;

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

Abstrait de la montre


Mais pourquoi vous forcer Ă  utiliser une horloge vidĂ©o? Vous pouvez aller plus loin et modifier notre code de synchronisation vidĂ©o afin que l'audio et la vidĂ©o n'essaient pas de se synchroniser. Imaginez quel gĂąchis ce sera si nous essayons de le faire avec une option de ligne de commande, comme dans FFplay. Donc, abstenons- nous: nous allons crĂ©er une nouvelle fonction wrapper, get_master_clock , qui vĂ©rifie la variable av_sync_type , puis appelle get_audio_clock , get_video_clock ou toute autre horloge qui pourrait l'utiliser. Nous pouvons mĂȘme utiliser une horloge d'ordinateur, que nous appelons 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;
...
}

Synchronisation audio


Maintenant, la partie la plus difficile: synchroniser l'audio avec l'horloge vidĂ©o. Notre stratĂ©gie consiste Ă  mesurer oĂč se trouve l'audio, Ă  le comparer avec l'horloge vidĂ©o, puis Ă  dĂ©couvrir combien d'Ă©chantillons nous devons ajuster, c'est-Ă -dire, devons-nous accĂ©lĂ©rer en supprimant les Ă©chantillons ou ralentir en ajoutant?

Nous exécutons la fonction synchronize_audio chaque fois que nous traitons chaque ensemble d'échantillons audio que nous obtenons afin de réduire ou d'augmenter correctement cet ensemble. Cependant, nous ne voulons pas synchroniser tout le temps, car le traitement audio se produit beaucoup plus souvent que le traitement des paquets vidéo. Donc, nous allons définir le nombre minimum d'appels consécutifs à la fonction synchronize_audioqui sont considérés comme non synchronisés avant que nous ne prenions la peine de faire quoi que ce soit. Bien entendu, comme la derniÚre fois, la «désynchronisation» signifie que l'horloge audio et l'horloge vidéo diffÚrent d'une quantité supérieure au seuil de synchronisation.

Nous allons donc utiliser un coefficient fractionnaire, disons, s , et maintenant, disons que nous avons obtenu Nensembles d'Ă©chantillons audio qui n'Ă©taient pas synchronisĂ©s. Le nombre d'Ă©chantillons que nous ne synchronisons pas peut Ă©galement varier considĂ©rablement, nous prenons donc la valeur moyenne de combien chacun d'eux n'est pas synchronisĂ©. Par exemple, le premier appel pourrait montrer que nous ne sommes pas synchronisĂ©s pendant 40 ms, le suivant pendant 50 ms et ainsi de suite. Mais nous n'allons pas prendre une moyenne simple, car les valeurs les plus rĂ©centes sont plus importantes que celles qui les prĂ©cĂšdent. Donc, nous allons utiliser un coefficient fractionnaire, disons, c , et rĂ©sumer les diffĂ©rences comme suit: diff_sum = new_diff + diff_sum * c . Lorsque nous sommes prĂȘts Ă  trouver la diffĂ©rence moyenne, nous calculons simplement avg_diff =diff_sum * (1 - c ).

Qu'est ce qui se passe ici? L'Ă©quation ressemble Ă  une sorte de magie. Eh bien, il s'agit essentiellement d'une moyenne pondĂ©rĂ©e utilisant une sĂ©rie gĂ©omĂ©trique comme poids. Je ne sais pas s'il y a un nom pour ça (j'ai mĂȘme vĂ©rifiĂ© sur WikipĂ©dia!), Mais pour plus d'informations, voici une explication (ou ici: weightedmean.txt ).

Voici Ă  quoi ressemble notre fonction:

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

Donc tout va bien pour nous; nous savons approximativement à quel point le son n'est pas cohérent avec la vidéo ou avec ce que nous utilisons comme montre. Donc, calculons maintenant combien d'échantillons nous devons ajouter ou supprimer en plaçant ce code dans la section "Réduire / étendre le code tampon":

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

N'oubliez pas que audio_length * ( sample_rate * # of channel * 2) est le nombre d'échantillons en audio_length secondes d'audio. Par conséquent, le nombre d'échantillons que nous voulons sera égal au nombre d'échantillons que nous avons déjà, plus ou moins le nombre d'échantillons correspondant à la durée pendant laquelle le son a été joué. Nous fixerons également une limite sur l'ampleur de notre correction, car si nous modifions trop notre tampon, ce sera trop ennuyeux pour l'utilisateur.

Correction du nombre d'Ă©chantillons


Maintenant, nous devons corriger le son. Vous avez peut-ĂȘtre remarquĂ© que notre fonction synchronize_audio renvoie une taille d'Ă©chantillon, qui nous indique ensuite le nombre d'octets Ă  envoyer au flux. Il nous suffit donc d'ajuster la taille de l'Ă©chantillon Ă  la valeur souhaitĂ©e. Cela fonctionne pour rĂ©duire la taille de l'Ă©chantillon. Mais si vous devez l'augmenter, nous ne pouvons pas simplement augmenter la taille de l'Ă©chantillon, car il n'y a plus de donnĂ©es dans le tampon! Par consĂ©quent, nous devons ajouter un peu. Mais quoi ajouter exactement? Il serait stupide d'essayer d'extrapoler l'audio, alors utilisons simplement l'audio que nous avons dĂ©jĂ , en ajoutant la valeur du dernier Ă©chantillon au tampon.

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

Maintenant, nous retournons la taille de l'échantillon, et nous avons terminé avec cette fonction. Il ne nous reste plus qu'à utiliser ceci:

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;

Tout ce que nous avons fait, c'est insérer un appel synchronize_audio . (Aussi, assurez-vous de vérifier le code source, dans lequel nous initialisons les variables que je n'ai pas pris la peine de définir.)

Et la derniÚre, avant de terminer: nous devons ajouter la condition "si" pour nous assurer de ne pas synchroniser la vidéo si c'est l'horloge principale:

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

Et il fonctionne! Assurez-vous de vérifier le fichier source pour initialiser toutes les variables que je n'ai pas pris la peine de définir ou d'initialiser. Compilez ensuite:

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

et le vol sera normal.

Dans la derniÚre leçon, nous allons revenir en arriÚre.






Leçon 7: Recherche ← ⇑ →


Liste complĂšte 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;

}

Traitement des commandes de recherche


Maintenant, nous allons ajouter des fonctionnalités de recherche dans notre lecteur, car c'est vraiment ennuyeux lorsque vous ne pouvez pas rembobiner le film. De plus, nous verrons à quel point il est facile d'utiliser la fonction av_seek_frame .

Nous allons faire «flĂ©chir» vers la gauche et la «droite» les flĂšches du clavier pour faire avancer et reculer un peu le film, et les flĂšches «haut» et «bas» sont dĂ©jĂ  plus importantes. «Un peu» - ce sera 10 secondes et «beaucoup» - tous les 60. Par consĂ©quent, nous devons configurer notre boucle principale afin qu'elle intercepte les Ă©vĂ©nements de frappe. Mais le fait est que lorsque nous obtenons une frappe, nous ne pouvons pas appeler directement av_seek_frame . Cela doit ĂȘtre fait dans notre boucle de dĂ©codage principale, boucle decode_thread. Par consĂ©quent, Ă  la place, nous ajouterons quelques valeurs Ă  la structure principale, qui contiendront une nouvelle position pour la recherche et quelques indicateurs de recherche:

  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

Maintenant, nous devons configurer notre boucle principale qui capture les frappes:

  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;

Pour intercepter une frappe, nous examinons d'abord si l'événement SDL_KEYDOWN s'est produit . Ensuite, nous vérifions quelle clé est reçue à l'aide de event.key.keysym.sym . DÚs que nous découvrons dans quelle direction nous cherchons, nous calculons une nouvelle heure, en ajoutant un incrément à la valeur de notre nouvelle fonction get_master_clock . Ensuite , nous appelons la stream_seek fonction pour définir les seek_pos valeurs , etc. Convertissez notre nouvelle heure en unités d' horodatage interne avcodec . Rappelez-vous que les horodatages dans les flux sont mesurés en images, pas en secondes, en utilisant la formule suivante: secondes = images * base de temps ( fps ).Par défaut, avcodec est défini sur 1 000 000 d'images par seconde (donc une position de 2 secondes aura un horodatage de 2 000 000). Pourquoi devons-nous convertir cette valeur - voir plus loin.

Voici notre fonction stream_seek . Notez que nous mettons le drapeau si nous revenons en arriĂšre:

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

Passons maintenant Ă  notre decode_thread , oĂč nous effectuons rĂ©ellement une recherche. Dans les fichiers source, vous pouvez voir que nous avons marquĂ© la zone "la recherche est en cours". Eh bien, nous allons le mettre lĂ  tout de suite.

La recherche est centrée sur la fonction av_seek_frame . Cette fonction prend comme argument le contexte de format, le flux, l'horodatage et le jeu d'indicateurs. La fonction recherchera l'horodatage que vous lui attribuez. L'unité d'horodatage est la base de temps du flux que vous passez à la fonction. Cependant, vous n'avez pas besoin de le transmettre au flux (indiqué en passant la valeur -1). Si vous faites cela, time_base sera dans l' unité de temps interne avcodecou 1000000fps. C'est pourquoi nous avons multiplié notre position par AV_TIME_BASE lorsque nous avons défini Seek_pos .

Cependant, parfois, vous pouvez (rarement) rencontrer des problĂšmes pour certains fichiers si vous passez av_seek_frame - 1 pour le flux, nous allons donc sĂ©lectionner le premier flux dans notre fichier et le transmettre Ă  av_seek_frame . N'oubliez pas que nous devons changer l'Ă©chelle de notre horodatage pour ĂȘtre dans le nouveau «systĂšme de coordonnĂ©es».

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 ) est une fonction qui met à l'échelle l'horodatage d'une base à une autre. Il calcule essentiellement a * b / c , mais cette fonction est utile car ce calcul conduit parfois à un débordement. AV_TIME_BASE_Q est une version fractionnée d' AV_TIME_BASE . Ils sont complÚtement différents: AV_TIME_BASE * time_in_seconds = avcodec_timestamp et AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (mais notez que AV_TIME_BASE_Qest en fait un objet AVRational , vous devez donc utiliser des fonctions q spéciales dans avcodec pour le traiter).

Nettoyage des tampons


Nous avons donc configurĂ© nos recherches correctement, mais nous n'avons pas encore terminĂ©. N'oubliez pas, avons-nous une file d'attente configurĂ©e pour accumuler des paquets? Maintenant que nous sommes dans un horodatage diffĂ©rent, nous devons effacer cette file d'attente, sinon la recherche dans le film ne fonctionnera pas! De plus, avcodec possĂšde ses propres tampons internes, qui doivent Ă©galement ĂȘtre vidĂ©s pour chaque flux.

Pour ce faire, vous devez d'abord écrire une fonction qui efface notre file d'attente de paquets. Ensuite, vous devez en quelque sorte indiquer au flux audio et vidéo qu'ils ont effacé les tampons internes avcodec . Nous pouvons le faire en plaçant un paquet spécial dans la file d'attente aprÚs son nettoyage, et quand ils (threads) découvriront ce paquet spécial, ils effaceront simplement leurs tampons.

Commençons par la fonction de réinitialisation. C'est vraiment assez simple, donc je vais juste vous montrer le code:

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

Maintenant que la file d'attente est effacée, ajoutez notre «paquet de nettoyage». Mais d'abord, il serait bien de définir ce que c'est et de le créer:

AVPacket flush_pkt;

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

Maintenant, mettez ce paquet dans la file d'attente:

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

(Cet extrait de code continue l'extrait de code ci-dessus pour decode_thread .) Nous devons également modifier packet_queue_put afin de ne pas dupliquer un package spécial pour le nettoyage:

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

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

Et puis dans le flux audio et vidéo, nous plaçons cet appel dans avcodec_flush_buffers juste aprÚs 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;
    }

L'extrait de code ci-dessus est exactement le mĂȘme pour le flux vidĂ©o, avec le remplacement de "audio" par "video".

Ça y est ...! Nous l'avons fait! Compilez votre lecteur:

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

et profitez de votre lecteur de film fait en moins de 1000 lignes de C!

Bien sĂ»r, il y a beaucoup de choses qui peuvent ĂȘtre ajoutĂ©es ou amĂ©liorĂ©es.






Postface ← ⇑ →


Donc, nous avons un joueur qui fonctionne, mais bien sĂ»r, il n'est pas aussi bon qu'il pourrait l'ĂȘtre. Il serait possible de modifier le fichier et d'ajouter beaucoup de choses utiles:

  • Avouons-le, ce joueur est nul. La version de ffplay.c sur laquelle il est basĂ© est complĂštement obsolĂšte et, par consĂ©quent, ce didacticiel doit ĂȘtre entiĂšrement rĂ©visĂ©. Si vous souhaitez passer Ă  des projets plus sĂ©rieux en utilisant les bibliothĂšques FFmpeg, je vous recommande fortement de vĂ©rifier la derniĂšre version de ffplay.c comme tĂąche suivante.
  • La gestion des erreurs dans notre code est terrible et peut ĂȘtre implĂ©mentĂ©e beaucoup mieux.
  • , , , . , paused , , . , , . av_read_play. - , . , , . : , ffplay.c.
  • .
  • . , , , , VOB-.
  • . , .
  • . .
  • , , , , YUV, time_base.
  • .
  • --, ; ffplay.c .


Si vous voulez en savoir plus sur FFmpeg, alors nous avons considéré loin de tout. L'étape suivante consiste à étudier le codage multimédia. Il est préférable de commencer avec le fichier output_example.c , que vous trouverez dans la distribution FFmpeg. Je pourrais déjà écrire un autre manuel sur ce sujet, mais il est peu probable qu'il dépasse ce guide.

UPD.Il y a longtemps, je n'ai pas mis Ă  jour ce texte, mais en attendant le monde ne reste pas immobile. Ce didacticiel ne nĂ©cessite que de simples mises Ă  jour d'API; trĂšs peu de choses ont changĂ© en termes de concepts de base. La plupart de ces mises Ă  jour ont en fait simplifiĂ© le code. Cependant, bien que j'aie parcouru le code et l'ai mis Ă  jour, FFplay est toujours supĂ©rieur Ă  ce joueur. La main sur le cƓur, nous l'admettons: dans ces leçons, nous avons Ă©crit un lecteur de film assez moche. Par consĂ©quent, si aujourd'hui (ou Ă  l'avenir) vous souhaitez amĂ©liorer ce didacticiel, je vous recommande de vous familiariser avec FFplay et de dĂ©couvrir ce qui manque. Je pense que cela concerne principalement l'utilisation de l'Ă©quipement vidĂ©o, mais il est fort possible que je passe Ă  cĂŽtĂ© d'autres Ă©lĂ©ments Ă©vidents. Peut-ĂȘtre qu'une comparaison avec le FFplay actuel conduirait Ă  une rĂ©Ă©criture radicale de certaines choses - je ne l'ai pas encore regardĂ©.

Mais je suis trĂšs fier qu'au fil des ans, mon travail ait beaucoup aidĂ©, mĂȘme en tenant compte du fait que les gens recherchaient souvent du code ailleurs. Je suis extrĂȘmement reconnaissant Ă  Chelyaev , qui s'est chargĂ© de remplacer toutes les fonctions obsolĂštes depuis que j'ai Ă©crit cette monographie il y a 8 (!) Ans.

Je me réjouis en espérant que ces leçons se sont avérées utiles et non ennuyeuses. S'il y a des suggestions, des erreurs, des plaintes, des remerciements, etc. concernant ce guide, veuillez m'écrire à dranger dog gmail dot com. Et oui, cela n'a aucun sens de me demander de vous aider avec votre projet FFmpeg. Il y a trop de lettres similaires .






Annexe 1. Liste des fonctions ← ⇑ →


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

Ouvre le nom du fichier multimédia, enregistre le contexte de format à l'adresse spécifiée dans ptr .

fmt : sinon NULL, définit le format de fichier.
buf_size : taille du tampon (facultatif).
options : AVDictionary est rempli avec les paramÚtres de AVFormatContext et du démultiplexeur.

void avformat_close_input(AVFormatContext **s)

Ferme le fichier multimédia. Cependant, il ne ferme pas les codecs.

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

Crée un contexte d'E / S pour utiliser la ressource spécifiée dans l' url .

s : pointeur vers l'endroit oĂč l' AVIOContext sera crĂ©Ă© . En cas d'Ă©chec, la valeur spĂ©cifiĂ©e est dĂ©finie sur NULL.
url : nom de la ressource à accéder.
flags : contrÎle l'ouverture de la ressource spécifiée dans l' url .
int_cb : interruption du rappel pour une utilisation au niveau du protocole.
options : un dictionnaire rempli de paramĂštres de protocole privĂ©s. Lorsque la fonction revient, le paramĂštre sera dĂ©truit et remplacĂ© par un dict contenant des options non trouvĂ©es. Peut ĂȘtre NULL.

int av_dup_packet(AVPacket *pkt)

Bien sûr, c'est un hack: si ce package n'a pas été alloué, nous le postons ici. Renvoie 0 en cas de succÚs ou AVERROR_NOMEM en cas d'échec.

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

Cette fonction recherche des informations de flux non Ă©videntes, telles que la frĂ©quence d'images. Ceci est utile pour les formats de fichiers sans en-tĂȘte tels que MPEG. Il est recommandĂ© d'appeler aprĂšs avoir ouvert le fichier. Retourne> = 0 en cas de succĂšs, AVERROR_ * en cas d'erreur.

AVFrame *avcodec_free_frame()

Ancien nom pour av_frame_free. Modifié en lavc 55.28.1.

void av_frame_free (AVFrame **frame)

LibÚre un cadre et tout objet alloué dynamiquement, par exemple, Extended_data.

void av_free(void *ptr)

LibÚre la mémoire allouée à l'aide de av_malloc () ou av_realloc (). Vous pouvez appeler cette fonction avec ptr == NULL. Il est recommandé d'appeler à la place av_freep ().

void av_freep(void *ptr)

LibÚre de la mémoire et définit le pointeur sur NULL. Utilise en interne av_free ().

void av_free_packet(AVPacket *pkt)

Enrouler autour de la méthode de destruction de paquets (pkt-> destruct).

int64_t av_gettime()

Obtenez l'heure actuelle en microsecondes.

void av_init_packet(AVPacket *pkt)

Initialisation des champs de package facultatifs.

void *av_malloc(unsigned int size)

Taille d'octet d'allocation de mémoire avec alignement adapté à tous les accÚs à la mémoire (y compris les vecteurs, si disponibles sur le CPU). av_malloc (0) doit renvoyer un pointeur non nul.

void *av_mallocz(unsigned int size)

Identique à av_malloc (), mais initialise la mémoire à zéro.

double av_q2d(AVRational a)

Double AVRational.

int av_read_frame(AVFormatContext *s, AVPacket *pkt)

Renvoie la trame de flux suivante. Les informations sont stockées sous forme de package dans pkt.

Le paquet retournĂ© est valide jusqu'au prochain av_read_frame () ou jusqu'Ă  av_close_input_file () et doit ĂȘtre libĂ©rĂ© en utilisant av_free_packet. Pour un package vidĂ©o contient exactement une image. Pour l'audio, il contient un nombre entier de trames si chaque trame a une taille fixe connue (par exemple, des donnĂ©es PCM ou ADPCM). Si les images audio sont de taille variable (par exemple, audio MPEG), elles contiennent une image.

pkt-> pts, pkt-> dts et pkt-> duration sont toujours dĂ©finis sur les valeurs correctes en unitĂ©s de AVStream.timebase (et il est supposĂ© que le format ne peut pas les fournir). pkt-> pts peut ĂȘtre AV_NOPTS_VALUE si le format vidĂ©o a des images B, il est donc prĂ©fĂ©rable de s'appuyer sur pkt-> dts si vous ne dĂ©ballez pas la charge utile.

Résultat retourné: 0, si tout va bien, <0, s'il y a une erreur ou la fin du fichier.

void av_register_all();

Enregistre tous les codecs de la bibliothĂšque.

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

Renvoie a * bq / cq .

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

Recherche une image clé à un horodatage.

stream_index : si stream_index est -1, le flux par défaut est sélectionné et l'horodatage est automatiquement converti des unités AV_TIME_BASE en une base de temps spécifique au flux.
horodatage : horodatage mesuré en unités de AVStream.time_base ou, si aucun flux n'est spécifié, puis en unités de AV_TIME_BASE.
drapeaux : définissez les paramÚtres concernant la direction et le mode de recherche:
AVSEEK_FLAG_ANY: recherchez dans n'importe quelle image, pas seulement les images clés.
AVSEEK_FLAG_BACKWARD: recherche dans la direction opposée.
AVSEEK_FLAG_BYTE: recherche basée sur la position en octets.

AVFrame *avcodec_alloc_frame()

Ancien nom pour av_frame_alloc. Modifié en lavc 55.28.1.

AVFrame *av_frame_alloc()

SĂ©lectionne un AVFrame et l'initialise. Peut ĂȘtre libĂ©rĂ© en utilisant av_frame_free ().

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

Décode une image audio d'avpkt en image. La fonction avcodec_decode_audio4 () décode un fichier audio d'AVPacket. Pour son décodage, un codec audio est utilisé, qui était associé à avctx en utilisant avcodec_open2 (). La trame décodée résultante est stockée dans l'AVFrame spécifié. Si la trame a été décompressée, elle mettra got_frame_ptr à 1.

Avertissement: le tampon d'entrĂ©e, avpkt-> data, doit ĂȘtre FF_INPUT_BUFFER_PADDING_SIZE plus grand que les octets de lecture rĂ©els, car certains lecteurs de flux binaires optimisĂ©s lisent 32 ou 64 bits Ă  la fois et peuvent lire jusqu'Ă  la fin.

avctx : contexte du codec.
frame : frame cible.
got_frame_ptr : target int, qui sera défini si le cadre a été décompressé.
AVPKT: AVPacket contenant de l'audio.

Résultat retourné: si une erreur est retournée, une valeur négative est retournée; sinon, le nombre d'octets utilisés à partir de l'AVPacket d'entrée est retourné.

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

Décode une image vidéo de buf en image. La fonction avcodec_decode_video2 () décode une image vidéo à partir d'un tampon d'entrée de taille buf_size. Pour son décodage, un codec vidéo est utilisé, qui était associé à avctx en utilisant avcodec_open2 (). L'image décodée résultante est enregistrée dans l'image.

Avertissement: les exemples d'alignement et les problĂšmes de tampon qui s'appliquent Ă  avcodec_decode_audio4 s'appliquent Ă©galement Ă  cette fonction.

avctx : contexte du codec.
image : AVFrame dans lequel la vidéo décodée sera enregistrée.
frameFinished : zĂ©ro si aucune trame ne peut ĂȘtre dĂ©compressĂ©e, sinon elle n'est pas Ă©gale Ă  zĂ©ro.
avpkt: AVPacket d'entrée contenant le tampon d'entrée. Vous pouvez créer un tel package à l'aide de av_init_packet (), puis, aprÚs avoir spécifié les données et la taille, certains décodeurs peuvent en outre avoir besoin d'autres champs, tels que drapeaux et AV_PKT_FLAG_KEY. Tous les décodeurs sont conçus pour utiliser le moins de champs possible.

RĂ©sultat retournĂ©: En cas d'erreur, une valeur nĂ©gative est renvoyĂ©e, sinon le nombre d'octets est utilisĂ© ou zĂ©ro si aucune trame ne peut ĂȘtre dĂ©compressĂ©e.

int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)

Une méthode d'accÚs simple pour obtenir best_effort_timestamp à partir d'un objet AVFrame.

AVCodec *avcodec_find_decoder(enum CodecID id)

Recherche un dĂ©codeur avec CodecID. Renvoie NULL en cas d'erreur. Il doit ĂȘtre appelĂ© aprĂšs avoir obtenu l'AVCodecContext requis Ă  partir du flux dans AVFormatContext Ă  l'aide de codecCtx-> codec_id.

void avcodec_flush_buffers(AVCodecContetx *avctx)

Rinçage du tampon. Appelé lors de la recherche ou du passage à un autre flux.

AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)

Attribue AVCodecContext et définit ses champs sur des valeurs par défaut.

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

Copiez les paramÚtres de l'AVCodecContext source dans l'AVCodecContext cible. Le contexte résultant du codec de destination sera fermé, c'est-à-dire vous devez appeler avcodec_open2 () avant d'utiliser cet AVCodecContext pour décoder / encoder des données vidéo / audio.

dest : doit ĂȘtre initialisĂ© avec avcodec_alloc_context3 (NULL), sinon il ne sera pas initialisĂ©.

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

Initialise avctx pour utiliser le codec spĂ©cifiĂ© dans le codec . Doit ĂȘtre utilisĂ© aprĂšs avcodec_find_decoder. Retourne zĂ©ro en cas de succĂšs et une valeur nĂ©gative en cas d'erreur.

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

Définit la structure vers laquelle l'image pointe, avec le tampon ptr , le format pix_fmt et la largeur et la hauteur spécifiées. Renvoie la taille des données d'image en octets.

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

Calcule le nombre d'octets nécessaires pour une image d'une largeur, d'une hauteur et d'un format d'image donnés.

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

Renvoie SwsContext pour une utilisation dans sws_scale.

srcW , srcH , srcFormat : largeur, hauteur et format des pixels souhaités.
dstW , dstH , dstFormat : largeur, hauteur et format des derniers pixels.
flags : la méthode de mise à l'échelle à utiliser.
Les options suivantes sont disponibles: SWS_FAST_BILINEAR, SWS_BILINEAR, SWS_BICUBIC, SWS_X, SWS_POINT, SWS_AREA, SWS_BICUBLIN, SWS_GAUSS, SWS_SINC, SWS_LANCZOS, SWS_SPLINE.
Les autres indicateurs incluent les indicateurs de capacité du processeur: SWS_CPU_CAPS_MMX, SWS_CPU_CAPS_MMX2, SWS_CPU_CAPS_3DNOW, SWS_CPU_CAPS_ALTIVEC.
D'autres indicateurs incluent (actuellement pas complÚtement implémentés) SWS_FULL_CHR_H_INT, SWS_FULL_CHR_H_INP et SWS_DIRECT_BGR.
Enfin, il y a SWS_ACCURATE_RND et peut-ĂȘtre le plus utile pour les dĂ©butants, SWS_PRINT_INFO.
Je n'ai aucune idĂ©e de ce que font la plupart d'entre eux. Peut-ĂȘtre m'Ă©cris-tu?
srcFilter , dstFilter : SwsFilter pour la source et la destination. SwsFilter permet le filtrage couleur / luminosité. La valeur par défaut est NULL.
param : devrait ĂȘtre un pointeur vers un tampon int [2] avec des coefficients. Non documentĂ©. Il semble ĂȘtre utilisĂ© pour modifier lĂ©gĂšrement les algorithmes de mise Ă  l'Ă©chelle standard. La valeur par dĂ©faut est NULL. Uniquement pour les experts!

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

Met à l'échelle les données en src selon nos paramÚtres dans notre SwsContext * c .
srcStride et dstStride sont les tailles de ligne source et destination.

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

Ajoute une fonction de rappel qui s'exĂ©cute aprĂšs un nombre spĂ©cifiĂ© de millisecondes. La fonction de rappel transmet l'intervalle de temporisation en cours et le paramĂštre fourni par l'utilisateur Ă  partir de l'appel SDL_AddTimer et renvoie l'intervalle de temporisation suivant. (Si la valeur de retour du rappel correspond Ă  la valeur transmise, le minuteur continue de fonctionner Ă  la mĂȘme vitesse.) Si la valeur de retour du rappel est 0, le minuteur est annulĂ©.
Une autre façon d'annuler le temporisateur en cours consiste à appeler SDL_RemoveTimer avec l'identifiant du temporisateur (qui a été renvoyé par SDL_AddTimer).

La fonction de rappel du minuteur peut s'exĂ©cuter sur un thread diffĂ©rent de votre programme principal et ne doit donc pas appeler de fonction Ă  partir d'elle-mĂȘme. Cependant, vous pouvez toujours appeler SDL_PushEvent.

Le degré de détail du temporisateur dépend de la plate-forme, mais vous devez vous attendre à au moins 10 ms, car il s'agit de la valeur la plus courante. Cela signifie que si vous demandez un temporisateur de 16 ms, le rappel commencera aprÚs environ 20 ms sur un systÚme non chargé. Si vous devez définir un indicateur qui signale la mise à jour des images à une vitesse de 30 images par seconde (toutes les 33 ms), vous pouvez définir une minuterie de 30 ms (voir l'exemple ci-dessous). Si vous utilisez cette fonction, vous devez passer SDL_INIT_TIMER à SDL_Init.

Renvoie la valeur d'identificateur du minuteur ajouté, ou NULL si une erreur se produit.

Format de rappel:
Uint32 callback ( Uint32, void * param)


int SDL_CondSignal(SDL_cond *cond)

Redémarrage de l'un des threads en attente de la variable de condition cond . Renvoie 0 en cas de succÚs et -1 en cas d'erreur.

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

DĂ©verrouillez le mutex fourni et attendez qu'un autre thread appelle SDL_CondSignal ou SDL_CondBroadcast pour la variable de condition cond, puis verrouillez Ă  nouveau le mutex. Le mutex doit ĂȘtre verrouillĂ© avant d'accĂ©der Ă  cette fonction. Renvoie 0 lorsqu'un signal est reçu ou -1 en cas d'erreur.

SDL_cond *SDL_CreateCond(void);

Crée une variable de condition.

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

SDL_CreateThread crée un nouveau thread d'exécution qui partage toute la mémoire globale de son parent, des gestionnaires de signaux, des descripteurs de fichiers, etc. Et il exécute la fonction fn , en lui passant les données du pointeur vide. Le thread se termine lorsque fn renvoie une valeur.

void SDL_Delay (Uint32 );

Attend le nombre de millisecondes spĂ©cifiĂ©. SDL_Delay attendra au moins l'heure spĂ©cifiĂ©e, mais peut-ĂȘtre plus longtemps en raison de la planification du systĂšme d'exploitation.
Remarque: attendez-vous à une granularité de retard d'au moins 10 ms. Certaines plateformes ont des mesures plus courtes, mais c'est l'option la plus courante.

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

SDL_CreateYUVOverlay crée une superposition YUV de la largeur, de la hauteur et du format spécifiés (pour une liste des formats disponibles, voir la structure de données SDL_Overlay) pour l'affichage fourni. Renvoie SDL_Overlay.

l'affichage devrait en fait ĂȘtre une surface dĂ©rivĂ©e de SDL_SetVideoMode, sinon cette fonction fonctionnera par dĂ©faut.

Le terme «superposition» est incorrect, car si la superposition n'est pas crĂ©Ă©e dans le matĂ©riel, le contenu de la surface d'affichage sous la zone oĂč la superposition est affichĂ©e sera Ă©crasĂ© lorsque la superposition est affichĂ©e.

int SDL_LockYUVOverlay(SDL_Overlay *overlay)

SDL_LockYUVOverlay bloque la superposition pour un accÚs direct aux données de pixels. Renvoie 0 en cas de succÚs ou -1 en cas d'erreur.

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)

DĂ©verrouille une superposition prĂ©cĂ©demment verrouillĂ©e. La superposition doit ĂȘtre dĂ©verrouillĂ©e avant de pouvoir ĂȘtre affichĂ©e.

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)

Place la superposition sur la surface spécifiée lors de sa création. La structure SDL_Rect dstrect définit la position et la taille de la destination. Si dstrect est plus ou moins une superposition, alors la superposition sera mise à l'échelle, ceci est optimisé pour une mise à l'échelle 2x. Renvoie 0 en cas de succÚs.

void SDL_FreeYUVOverlay(SDL_Overlay *overlay)

LibÚre la superposition créée par SDL_CreateYUVOverlay.

int SDL_Init(Uint32 flags);

Initialise le SDL. Cela doit ĂȘtre appelĂ© avant toutes les autres fonctions SDL. Le paramĂštre flags spĂ©cifie les parties du SDL Ă  initialiser.

SDL_INIT_TIMER - initialise le sous-systĂšme de temporisation.
SDL_INIT_AUDIO - initialise le sous-systĂšme audio.
SDL_INIT_VIDEO - initialise le sous-systÚme vidéo.
SDL_INIT_CDROM - initialise le sous-systĂšme du CD-ROM.
SDL_INIT_JOYSTICK - initialise le sous-systĂšme du joystick.
SDL_INIT_EVERYTHING - Initialise tout ce qui précÚde.
SDL_INIT_NOPARACHUTE - ne permet pas à SDL de détecter les erreurs fatales.
SDL_INIT_EVENTTHREAD - lance le gestionnaire d'événements dans un thread séparé.

Renvoie -1 en cas d'erreur ou 0 en cas de succÚs. Vous pouvez obtenir un message d'erreur étendu en appelant SDL_GetError. Une cause typique d'une erreur est l'utilisation d'un affichage spécifique sans le support correspondant pour le sous-systÚme, par exemple, l'absence d'un pilote de souris lors de l'utilisation d'un tampon de trame avec le périphérique. Dans ce cas, vous pouvez soit compiler SDL sans souris, soit définir la variable d'environnement "SDL_NOMOUSE = 1" avant de démarrer l'application.

SDL_mutex *SDL_CreateMutex(void);

Crée un nouveau mutex déverrouillé.

int SDL_LockMutex(SDL_mutex *mutex)

SDL_LockMutex est un alias pour SDL_mutexP. Il bloque un mutex crĂ©Ă© prĂ©cĂ©demment Ă  l'aide de SDL_CreateMutex. Si le mutex est dĂ©jĂ  bloquĂ© par un autre thread, alors SDL_mutexP ne renvoie pas de valeur tant que le thread bloquĂ© par lui ne le dĂ©verrouille pas (Ă  l'aide de SDL_mutexV). Lorsque le mutex est appelĂ© Ă  nouveau, SDL_mutexV (alias SDL_UnlockMutex) doit ĂȘtre appelĂ© un nombre Ă©gal de fois pour remettre le mutex dans un Ă©tat dĂ©verrouillĂ©. Renvoie 0 en cas de succĂšs ou -1 en cas d'erreur.

int SDL_UnlockMutex(SDL_Mutex *mutex)

DĂ©verrouillage Mutex.

int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

Cette fonction ouvre l'unitĂ© audio avec les paramĂštres requis et renvoie 0 en cas de succĂšs, en plaçant les paramĂštres matĂ©riels rĂ©els dans la structure vers laquelle elle pointe finalement. Si une valeur NULL est reçue, les donnĂ©es audio transmises Ă  la fonction de rappel seront garanties d'avoir le format requis et, si nĂ©cessaire, seront automatiquement converties au format audio matĂ©riel. Cette fonction renvoie -1 si le pĂ©riphĂ©rique audio n'a pas pu ĂȘtre ouvert ou le flux audio n'a pas pu ĂȘtre configurĂ©.

Pour ouvrir un périphérique audio, vous devez créer le SDL_AudioSpec souhaité. Ensuite, vous devez remplir cette structure avec les spécifications audio souhaitées.

desire-> freq : fréquence sonore souhaitée en échantillons par seconde.
désiré-> format : format audio souhaité (voir SDL_AudioSpec).
souhaité-> canaux: Canaux requis (1 pour mono, 2 pour stéréo, 4 pour son surround, 6 pour son surround avec centrage et LFE).
dĂ©sirĂ©-> Ă©chantillons : taille de tampon audio souhaitĂ©e dans les Ă©chantillons. Ce nombre doit ĂȘtre une puissance de deux et peut ĂȘtre ajustĂ© par le pilote audio Ă  une valeur plus adaptĂ©e au matĂ©riel. Les valeurs optimales vont de 512 Ă  8192 inclus, selon l'application et la vitesse du processeur. Des valeurs plus petites entraĂźnent des temps de rĂ©ponse plus rapides, mais peuvent entraĂźner de mauvaises performances si l'application effectue un traitement intensif et ne peut pas remplir le tampon audio Ă  temps. L'Ă©chantillon stĂ©rĂ©o se compose des canaux droit et gauche dans l'ordre LR. Veuillez noter que le nombre d'Ă©chantillons est directement liĂ© au temps en utilisant la formule suivante: ms = (Ă©chantillons * 1000) / freq .
dĂ©sirĂ©-> rappel : doit ĂȘtre rĂ©glĂ© sur une fonction qui sera appelĂ©e lorsque l'unitĂ© audio sera prĂȘte Ă  recevoir des donnĂ©es supplĂ©mentaires. Le pointeur vers le tampon audio et la longueur en octets du tampon audio sont transmis. Cette fonction est gĂ©nĂ©ralement exĂ©cutĂ©e dans un thread sĂ©parĂ©, et il est donc nĂ©cessaire de protĂ©ger les structures de donnĂ©es auxquelles elle accĂšde en appelant SDL_LockAudio et SDL_UnlockAudio dans le code. Le prototype de rappel est un rappel vide ( void * userdata , Uint8 * stream , int len ) . userdata - un pointeur stockĂ© dans le champ userdata SDL_AudioSpec. courantEst un pointeur vers le tampon audio que vous souhaitez remplir avec des informations, et len est la longueur du tampon audio en octets.
required-> userdata : ce pointeur est passé comme premier paramÚtre à la fonction de rappel.

SDL_OpenAudio lit ces champs à partir de la structure SDL_AudioSpec souhaitée transmise à la fonction et essaie de trouver la configuration audio qui correspond à votre désir. Comme mentionné ci-dessus, si le paramÚtre résultant est NULL, alors SDL est converti des paramÚtres sonores souhaités en paramÚtres d'équipement pendant la lecture.

Si NULL est renvoyĂ©, alors la SDL_AudioSpec requise est votre spĂ©cification de travail, sinon la SDL_AudioSpec rĂ©sultante devient une spĂ©cification de travail et la spĂ©cification souhaitĂ©e peut ĂȘtre supprimĂ©e. Les donnĂ©es de la spĂ©cification de travail sont utilisĂ©es lors de la construction de SDL_AudioCVT pour convertir les donnĂ©es tĂ©lĂ©chargĂ©es au format d'Ă©quipement.

SDL_OpenAudio calcule les champs de taille et de silence pour les spécifications souhaitées et résultantes. Le champ taille stocke la taille totale du tampon audio en octets, tandis que silence stocke la valeur utilisée pour représenter le silence dans le tampon audio

Le pĂ©riphĂ©rique audio commence Ă  lire le silence lorsqu'il est ouvert et doit ĂȘtre allumĂ© pour la lecture en appelant SDL_PauseAudio (0) lorsque vous ĂȘtes prĂȘt Ă  appeler la fonction de rappel audio. Étant donnĂ© que le pilote audio peut modifier la taille demandĂ©e du tampon audio, vous devez sĂ©lectionner les tampons de mixage locaux aprĂšs avoir ouvert le pĂ©riphĂ©rique audio.

void SDL_PauseAudio(int pause_on)

Cette fonction met en pause et arrĂȘte le traitement du rappel audio. Il doit ĂȘtre appelĂ© avec pause_on = 0 aprĂšs avoir ouvert le pĂ©riphĂ©rique audio afin de commencer la lecture du son. Cela vous permet d'initialiser en toute sĂ©curitĂ© les donnĂ©es de la fonction de rappel aprĂšs avoir ouvert l'unitĂ© audio. Le silence sera enregistrĂ© sur l'appareil audio pendant une pause.

int SDL_PushEvent(SDL_Event *event)

Une file d'attente d'Ă©vĂ©nements qui est rĂ©ellement utilisĂ©e comme canal de communication bidirectionnel. Non seulement les Ă©vĂ©nements peuvent ĂȘtre lus dans la file d'attente, mais l'utilisateur peut Ă©galement y placer leurs propres Ă©vĂ©nements. Un Ă©vĂ©nement est un pointeur sur la structure de l'Ă©vĂ©nement que vous souhaitez mettre en file d'attente. L'Ă©vĂ©nement est copiĂ© dans la file d'attente et l'appelant peut gĂ©rer la mĂ©moire pointĂ©e aprĂšs avoir renvoyĂ© SDL_PushEvent. Cette fonction est orientĂ©e thread et peut ĂȘtre appelĂ©e en toute sĂ©curitĂ© Ă  partir d'autres threads. Renvoie 0 en cas de succĂšs ou -1 si l'Ă©vĂ©nement n'a pas pu ĂȘtre distribuĂ©.

int SDL_WaitEvent(SDL_Event *event)

Attend indéfiniment le prochain événement disponible, renvoyant 0 si une erreur s'est produite lors de l'attente des événements, 1 sinon. Si l'événement n'est pas NULL, l'événement suivant est supprimé de la file d'attente et stocké dans cette zone.

void SDL_Quit()

DĂ©sactive tous les sous-systĂšmes SDL et libĂšre les ressources qui lui sont allouĂ©es. Cela doit toujours ĂȘtre appelĂ© avant de quitter.

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

RĂ©glage du mode vidĂ©o avec la largeur, la hauteur et les bits de pixel spĂ©cifiĂ©s. À partir de SDL 1.2.10, si la largeur et la hauteur sont Ă©gales Ă  0, il utilisera la largeur et la hauteur du mode vidĂ©o actuel (ou le mode bureau si le mode n'est pas dĂ©fini). Si bitsperpixel vaut 0, il est traitĂ© comme les bits d'affichage actuels par pixel. Le paramĂštre flags est le mĂȘme que le champ flags de la structure SDL_Surface. Ou une combinaison des valeurs suivantes:

SDL_SWSURFACE - crée une surface vidéo dans la mémoire systÚme.
SDL_HWSURFACE - crée une surface vidéo dans la mémoire vidéo.
SDL_ASYNCBLIT - permet l'utilisation de mises à jour asynchrones de la surface d'affichage. Cela ralentit généralement le travail sur les ordinateurs à processeur unique, mais peut augmenter la vitesse des systÚmes SMP.
SDL_ANYFORMAT - gĂ©nĂ©ralement, si une surface vidĂ©o avec les bits demandĂ©s par pixel (bpp - Ă  partir de bits par pixel) n'est pas disponible, SDL Ă©mule la vidĂ©o avec une surface ombrĂ©e. Passer SDL_ANYFORMAT empĂȘche cela et force le SDL Ă  utiliser la surface de la vidĂ©o, quelle que soit sa profondeur en pixels.
SDL_HWPALETTE - Fournit un accĂšs exclusif SDL Ă  la palette. Sans cet indicateur, vous ne pouvez pas toujours obtenir les couleurs que vous demandez Ă  l'aide de SDL_SetColors ou SDL_SetPalette.
SDL_DOUBLEBUF - active le double tampon matĂ©riel; valable uniquement avec SDL_HWSURFACE. Un appel Ă  SDL_Flip inversera les tampons et rafraĂźchira l'Ă©cran. Tout le dessin aura lieu sur une surface qui n'est pas actuellement affichĂ©e. Si la double mise en mĂ©moire tampon ne peut pas ĂȘtre activĂ©e, alors SDL_Flip exĂ©cutera simplement SDL_UpdateRect en plein Ă©cran.
SDL_FULLSCREEN SDL - essayez d'utiliser le mode plein Ă©cran. Si la modification de la rĂ©solution matĂ©rielle n'est pas possible (pour une raison quelconque), la rĂ©solution supĂ©rieure suivante sera utilisĂ©e et la fenĂȘtre d'affichage sera centrĂ©e sur un fond noir.
SDL_OPENGL - crée un contexte de rendu OpenGL. Il est supposé que les attributs vidéo OpenGL avec SDL_GL_SetAttribute sont prédéfinis.
SDL_OPENGLBLIT - crĂ©e un contexte de rendu OpenGL, comme dĂ©crit ci-dessus, mais autorise les opĂ©rations de blitting normales. Une surface d'Ă©cran (2D) peut avoir un canal alpha et SDL_UpdateRects doit ĂȘtre utilisĂ© pour mettre Ă  jour les changements de surface d'Ă©cran. REMARQUE. Cette option est enregistrĂ©e uniquement pour des raisons de compatibilitĂ© et sera supprimĂ©e dans les futures versions. Non recommandĂ© pour une utilisation dans un nouveau code.
SDL_RESIZABL -crĂ©er une fenĂȘtre redimensionnable. Lorsque la taille de la fenĂȘtre est modifiĂ©e par l'utilisateur, l'Ă©vĂ©nement SDL_VIDEORESIZE est gĂ©nĂ©rĂ© et SDL_SetVideoMode peut ĂȘtre appelĂ© Ă  nouveau avec une nouvelle taille.
SDL_NOFRAME Si possible, SDL_NOFRAME force SDL Ă  crĂ©er une fenĂȘtre sans titre ni encadrĂ©e. Ce drapeau est automatiquement dĂ©fini en mode plein Ă©cran.
Remarque. Peu importe quels indicateurs SDL_SetVideoMode peuvent satisfaire, ils sont définis dans l'élément flags de la surface renvoyée.
REMARQUE. Le pixel bit 24 utilise une représentation compactée de 3 octets par pixel. Pour le mode 4 octets par pixel le plus courant, utilisez un pixel 32 bits. Curieusement, 15 et 16 demanderont un mode 2 octets par pixel, mais avec des formats de pixels différents.
REMARQUE. Utilisez SDL_SWSURFACE si vous prĂ©voyez d'effectuer des manipulations de pixels distinctes ou de faire glisser des surfaces Ă  l'aide de canaux alpha et que vous avez besoin d'une frĂ©quence d'images Ă©levĂ©e. Lorsque vous utilisez des surfaces matĂ©rielles (SDL_HWSURFACE), le SDL copie les surfaces de la mĂ©moire vidĂ©o dans la mĂ©moire systĂšme lorsque vous les verrouillez et inversement lorsque vous les dĂ©verrouillez. Cela peut entraĂźner une baisse significative des performances. (Gardez Ă  l'esprit que vous pouvez rechercher une surface matĂ©rielle mais toujours obtenir une surface logicielle. De nombreuses plates-formes ne peuvent fournir une surface matĂ©rielle que lorsque vous utilisez SDL_FULLSCREEN.) SDL_HWSURFACE est mieux utilisĂ© lorsque les surfaces que vous clignoterez peuvent Ă©galement ĂȘtre stockĂ©es dans la mĂ©moire vidĂ©o.
REMARQUE. Si vous souhaitez contrĂŽler la position sur l'Ă©cran lors de la crĂ©ation de la surface de la fenĂȘtre, vous pouvez le faire en dĂ©finissant les variables d'environnement «SDL_VIDEO_CENTERED = center» ou «SDL_VIDEO_WINDOW_POS = x, y». Vous pouvez les installer via SDL_putenv.

Valeur de retour: surface du tampon de trame ou NULL en cas d'Ă©chec. La surface retournĂ©e est libĂ©rĂ©e par SDL_Quit et ne doit pas ĂȘtre libĂ©rĂ©e par l'appelant.
REMARQUE. Cette rÚgle inclut les appels consécutifs à SDL_SetVideoMode (c'est-à-dire le redimensionnement) - la surface existante sera libérée automatiquement.






Annexe 2. Structures de donnĂ©es ← ⇑ →



AVCodecContext

Toutes les informations sur le codec du flux, de AVStream-> codec. Quelques attributs importants:

AVRational time_base : nombre d'images par seconde
int sample_rate : Ă©chantillons par seconde
int canal : nombre de canaux

Voir la liste complÚte (trÚs impressionnante) ici ( archives web, car le lien d'origine est déjà inexistant ). De nombreux paramÚtres sont utilisés principalement pour le codage, pas pour le décodage.

AVFormatContext

Champs de données:

const AVClass * av_class
AVInputFormat * iformat
AVOutputFormat * oformat
void * priv_data :
ByteIOContext pb : utilisé pour la manipulation de fichiers de bas niveau.
unsigned int nb_streams : nombre de threads dans le fichier.
Flux AVStream * [MAX_STREAMS] : les données de chaque flux sont stockées ici.
char nom de fichier [1024]: mais qu'en est-il sans (dans l'original - duh ).

Informations sur le fichier: horodatage
int64_t : char title [512]:

ombles auteur [512]:
omble chevalier droit d' auteur [512]:
omble chevalier commentaire [512]:
ombles album [512]:
int année :
int piste :
ombles genre [32]:

int ctx_flags : Les
valeurs possibles sont AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, AVFMT_RAWPICTURE, AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_GENERIC_INDEX
AVPacketList * packet_buffer : ce tampon est nécessaire uniquement lorsque les paquets sont déjà tamponnés mais non décodés, par exemple, pour recevoir les paramÚtres du codec dans les flux mpeg.
int64_tstart_time : lors du décodage: la position de la premiÚre trame du composant, en fractions de seconde, AV_TIME_BASE. Ne définissez JAMAIS cette valeur directement: elle est déduite des valeurs d'AVStream.
int64_t durée: décodage : durée du flux, en fractions d'AV_TIME_BASE. Ne définissez JAMAIS cette valeur directement: elle est déduite des valeurs d'AVStream.
int64_t file_size : taille totale du fichier, 0 si inconnu.
int bit_rate : decoding: débit binaire total du flux en bit / s, 0 s'il n'est pas disponible. NE le définissez JAMAIS directement si file_size et la durée connue dans ffmpeg peuvent le calculer automatiquement.
AVStream * cur_st
const uint8_t * cur_ptr
int cur_len
AVPacket cur_pkt :
int64_t data_offset :
int index_built : décalage du premier paquet.
int mux_rate :
int packet_size :
int preload :
int max_delay :
int loop_output : nombre de boucles de sortie dans les formats pris en charge.
drapeaux int :
int loop_input :
unsigned int probesize : décodage: taille des données de l'échantillon; non utilisé dans le codage.
int max_analyze_duration : durĂ©e maximale en unitĂ©s de AV_TIME_BASE pendant laquelle les donnĂ©es d'entrĂ©e doivent ĂȘtre analysĂ©es dans av_find_stream_info ()
const uint8_t * clé :
int keylen :

AVIOContext

Contexte d'E / S pour accéder aux ressources.

const AVClass * av_class : classe pour les paramÚtres privés.
unsigned char * buffer : début du tampon.
int buffer_size : taille maximale du tampon.
unsigned char * buf_ptr : position actuelle dans le tampon.
unsigned char * buf_end : Les donnĂ©es peuvent ĂȘtre plus petites que buffer + buffer_size si la fonction de lecture a retournĂ© moins de donnĂ©es que demandĂ©, par exemple.
void * opaque : pointeur privé passé à 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 (* cherche) (void * opaque, int64_t offset, int d'oĂč) :
int64_t pos : position dans le fichier du tampon courant.
int must_flush : true si la prochaine recherche doit ĂȘtre rĂ©initialisĂ©e.
int eof_reached : true si la fin du fichier est atteinte.
int write_flag : true si ouvert pour l'Ă©criture.
int max_packet_size :
unsigned long checksum :
unsigned char * checksum_ptr :
unsigned long (* update_checksum) (unsigned long checksum, const uint8_t * buf, unsigned int size) :
int error : contient le code d'erreur ou 0 si aucune erreur ne s'est produite.
int (* read_pause) (void * opaque, int pause): mettre en pause ou reprendre la lecture pour les protocoles de streaming réseau, par exemple.
int64_t (* read_seek) (void * opaque, int stream_index, int64_t timestamp, int flags) : recherchez l'horodatage spécifié dans le flux avec l'index stream_index spécifié.
int recherche : combinaison des drapeaux AVIO_SEEKABLE_ ou 0 lorsque le flux n'est pas consultable.
int64_t maxsize : taille maximale du fichier utilisée pour limiter la sélection. Ce champ est interne à libavformat et son accÚs depuis l'extérieur est interdit.
int direct : avio_read et avio_write doivent ĂȘtre exĂ©cutĂ©s directement dans la mesure du possible, et ne pas passer par le tampon, et avio_seek appellera toujours directement la fonction de recherche principale.
int64_t bytes_read: statistiques de lecture d'octets Ce champ est interne à libavformat et l'accÚs externe est refusé.
int seek_count : statistiques de recherche. Ce champ est interne à libavformat et son accÚs depuis l'extérieur est interdit.
int writeout_count : écrire des statistiques. Ce champ est interne à libavformat et son accÚs depuis l'extérieur est interdit.
int orig_buffer_size : la taille du tampon d'origine utilisée en interne aprÚs vérification et retour pour réinitialiser la taille du tampon. Ce champ est interne à libavformat et son accÚs depuis l'extérieur est interdit.

AVDictionary

Utilisé pour passer des paramÚtres à ffmpeg.

nombre
entier : AVDictionaryEntry * elems :

AVDictionaryEntry

Utilisé pour stocker les entrées du dictionnaire dans AVDictionary.

char * ket :
char * valeur :

AVFrame

Cette structure dépend du type de codec et est donc déterminée dynamiquement. Cependant, il existe des propriétés et des méthodes communes pour cette structure:

uint8_t * data [4] :
int linesize [4] : information stride.
uint8_t * base [4] :
int key_frame :
int pict_type :
int64_t pts : ce ne sont pas les pts attendus lors du décodage.
int coded_picture_number :
int display_picture_number :
int qualité :
int age :
int référence :
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 : données utilisateur
uint64_t erreur [4] :
int type :
int repeat_pict : vous demande de répéter l'image un nombre spécifié de fois.
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

Structure dans laquelle les donnĂ©es brutes des paquets sont stockĂ©es. Ces donnĂ©es doivent ĂȘtre transfĂ©rĂ©es vers avcodec_decode_audio2 ou avcodec_decode_video pour recevoir une trame.

int64_t pts : horodatage de présentation en unités de base de temps.
int64_t dts : horodatage de la décompression en unités de time_base.
uint8_t * data : données brutes.
int size : taille des données.
int stream_index : flux dont AVPacket est issu, basé sur la quantité dans AVFormatContext.
drapeaux int : PKT_FLAG_KEY est défini si le paquet est une image clé.
int duration : durée de la présentation en unités de time_base (0 si non disponible)
void (* destruct) (struct AVPacket *) : fonction de libération des ressources pour ce package (av_destruct_packet par défaut).
void * priv :
int64_t pos : position d'octet dans le flux, -1 si inconnu.

AVPacketList

Une simple liste chaßnée pour les packages.

AVPacket pkt :
AVPacketList * suivant :

AVPicture

Cette structure est exactement la mĂȘme que les deux premiers Ă©lĂ©ments de donnĂ©es AVFrame, elle est donc souvent supprimĂ©e. Couramment utilisĂ© dans les fonctions SWS.

uint8_t * data [4] :
int linesize [4] : le nombre d'octets dans la chaĂźne.

AVRational

Structure simple pour représenter des nombres rationnels.

int num : numérateur.
int den : dénominateur.

AVStream

La structure du flux. Vous utiliserez probablement ces informations dans le codec le plus souvent.

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 :
un disque ne peut pas ignorer le paquet en démultiplexage.
float quality :
int64_t start_time :
int64_t duration:
Langue Char [4] :
int need_parsing : 1 -> besoin parsing complĂšte, 2 -> parse tĂȘtes uniquement, sans remballer
AVCodecParserContext * analyseur :
int64_t cur_dts :
int last_IP_duration :
int64_t last_IP_pts :
AVIndexEntry * index_entries :
int nb_index_entries :
un unsigned int index_entries_allocated_size :
int64_t nb_frames : nombre de trames dans ce flux (si connu) ou 0
int64_t pts_buffer [MAX_REORDER_DELAY + 1] :

ByteIOContext

Une structure qui stocke des informations de bas niveau sur un fichier vidéo.

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 (* cherche) (void * opaque, offset_t offset, int d'oĂč) :
offset_t pos :
int must_flush :
int eof_reached :
int write_flag :
int is_streamed :
int max_packet_size :
unsigned long checksum :
unsigned char * checksum_ptr :
unsigned long (* update_checksum) (unsigned long checksum:
const uint8_t * buf, unsigned int size)
:
int error : contient le code d'erreur ou 0 si aucune erreur ne s'est produite.

SDL_AudioSpec

Utilisé pour décrire le format de certaines données audio.

freq : fréquence sonore en échantillons par seconde.
format : format de données audio.
canaux : nombre de canaux: 1 - mono, 2 - stéréo, 4 surround, 6 surround avec centrage et
silence LFE : valeur de silence du tampon sonore (calculée).
samples : taille du buffer audio dans les samples.
taille : la taille du tampon audio en octets (calculée).
callback (..) : fonction de rappel pour remplir le buffer audio.
userdata : un pointeur vers les données utilisateur qui sont passées à la fonction de rappel.

Les valeurs de format suivantes sont

valides : AUDIO_U8 - ​​Échantillons non signĂ©s 8 bits.
AUDIO_S8 - échantillons 8 bits signés.
AUDIO_U16 ou AUDIO_U16LSB - non pris en charge par tout le matériel (ordre des octets faibles 16 bits non signé).
AUDIO_S16 ou AUDIO_S16LS - non pris en charge par tous les matériels (16 bits avec ancien ordre d'octets)
AUDIO_U16MSB - non pris en charge par tous les matériels (big-endian 16 bits non signé).
AUDIO_S16MS - non pris en charge par tout le matériel (16 bits avec ordre d'octets élevé).
AUDIO_U16SYS: AUDIO_U16LSB ou AUDIO_U16MSB - selon le processeur matériel.
AUDIO_S16SYS: AUDIO_S16LSB ou AUDIO_S16MSB - selon le processeur matériel.

SDL_Event

La structure de base pour les événements.

type : type d'événement.
active : événement d'activation (voir SDL_ActiveEvent).
key : événement clavier (voir SDL_KeyboardEvent).
motion : événement de mouvement de la souris (voir SDL_MouseMotionEvent).
bouton : événement de clic de souris (voir SDL_MouseButtonEvent).
jaxis : événement de mouvement de l'axe du joystick (voir SDL_JoyAxisEvent).
jball : événement de mouvement du joystick trackball (voir SDL_JoyBallEvent).
jhat : Ă©vĂ©nement de dĂ©placement de l'en-tĂȘte du joystick (voir SDL_JoyHatEvent).
jbutton : événement d'appuyer sur le bouton du joystick (voir SDL_JoyButtonEvent).
redimensionner: ÉvĂ©nement de redimensionnement de la fenĂȘtre d'application (voir SDL_ResizeEvent).
expose : Ă©vĂ©nement d'ouverture de fenĂȘtre d'application (voir SDL_ExposeEvent).
quit : événement de demande de sortie d'application (voir SDL_QuitEvent).
user : événement utilisateur (voir SDL_UserEvent).
syswm : Ă©vĂ©nement de gestionnaire de fenĂȘtres non dĂ©fini (voir SDL_SysWMEvent).

Voici les types d'événements. Voir la documentation SDL pour plus d'informations:

SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN / the UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN / UP
UPLLMouseButtonEvent
SDL_JOYAXISMOTION SDL_JoyAJOEL_JO
SDL_JoyHatEvent SDL_JOYHATMOTION
SDL_JOYBUTTONDOWN / the UP SDL_JoyButtonEvent
SDL_VIDEORESIZE
SDL_ResizeEvent SDL_VIDEOEXPOSE
SDL_ExposeEvent SDL_Quit
SDL_QuitEvent SDL_USEREVENT
SDL_USEREVS SDL_USERES

SDL_Overlay

Superposition YUV.

format : format de superposition (voir ci-dessous).
w, h : largeur / hauteur de la superposition.
plans : le nombre de plans Ă  superposer. Habituellement, soit 1 ou 3.
emplacements : un tableau de retraits, un pour chaque plan. L'indentation est la longueur de la chaĂźne en octets.
pixels : un tableau de pointeurs de donnĂ©es pour chaque plan. La superposition doit ĂȘtre verrouillĂ©e avant d'utiliser ces pointeurs.
hw_overlay : défini sur 1 si la superposition est accélérée par le matériel.

SDL_Rect

La zone rectangulaire.

Sint16 x, y : position du coin supérieur gauche du rectangle.
Uint16 w, h : la largeur et la hauteur du rectangle.

SDL_Rect définit une zone rectangulaire de pixels. Il est utilisé par SDL_BlitSurface pour identifier les zones de blitting et certaines autres fonctionnalités vidéo.

SDL_Surface

Structure graphique du cÎté extérieur (surface).

Uint32 drapeaux : Drapeaux des stotrons externes. Uniquement pour la lecture.
Format SDL_PixelFormat * : lecture seule.
int w, h : largeur et hauteur. Uniquement pour la lecture.
Pas Uint16 : pas. Uniquement pour la lecture.
void * pixels : un pointeur sur les données réelles des pixels. Pour l'enregistrement uniquement.
SDL_Rect clip_rect : l'extérieur rectangulaire du clip. Uniquement pour la lecture.
int refcount : utilisé pour allouer de la mémoire. Surtout pour la lecture.
Cette structure contient également des champs privés non représentés ici.

SDL_Surface reprĂ©sente une zone de mĂ©moire "graphique" qui peut ĂȘtre dessinĂ©e. La trame de tampon vidĂ©o est renvoyĂ©e en tant que SDL_Surface Ă  l'aide de SDL_SetVideoMode et SDL_GetVideoSurface. Les champs w et h sont des valeurs reprĂ©sentant la largeur et la hauteur de la surface en pixels. Le champ de pixels est un pointeur vers les donnĂ©es de pixels rĂ©elles. Remarque: la surface doit ĂȘtre verrouillĂ©e (via SDL_LockSurface) avant d'accĂ©der Ă  ce champ. Le champ clip_rect est le rectangle dĂ©coupĂ© dĂ©fini par SDL_SetClipRect.

Le champ indicateur prend en charge les valeurs OR suivantes:

SDL_SWSURFACE - l'extérieur est stocké dans la mémoire systÚme.
SDL_HWSURFACE - Le cÎté externe est stocké dans la mémoire vidéo.
SDL_ASYNCBLIT - L'extérieur utilise l'éblouissement asynchrone, si possible.
SDL_ANYFORMAT - Tout format de pixel (surface d'affichage) est autorisé.
SDL_HWPALETTE - La surface a une palette exclusive.
SDL_DOUBLEBUF - surface Ă  double tampon (surface d'affichage).
SDL_FULLSCREEN - surface plein Ă©cran (surface d'affichage).
SDL_OPENGL - la surface a un contexte OpenGL (surface d'affichage).
SDL_OPENGLBLIT - la surface prend en charge le blining OpenGL (surface d'affichage). REMARQUE. Cette option est uniquement pour la compatibilité et n'est pas recommandée pour le nouveau code.
SDL_RESIZABLE - le redimensionnement est possible pour une surface (surface d'affichage).
SDL_HWACCEL - le blit de surface utilise l'accélération matérielle.
SDL_SRCCOLORKEY - La superficialité utilise le mélange des couleurs.
SDL_RLEACCEL - le dégradé des couleurs est accéléré à l'aide de RLE.
SDL_SRCALPHA - Surface Blyth utilise le mélange alpha.
SDL_PREALLOC - La surface utilise de la mémoire pré-allouée.

SDL_Thread

Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / sdl_thread_c.h dans le code source pour plus d'informations.

SDL_cond

Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / <system> /SDL_syscond.c dans le code source pour plus d'informations.

SDL_mutex

Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / <system> /SDL_sysmutex.c dans le code source pour plus d'informations.






Liens ← ⇑


Un didacticiel FFmpeg et SDL ou comment Ă©crire un

lecteur vidéo en moins de 1 000 lignes FFmpeg , SDL FFmpeg HomePage SDL HomePage











A lire également sur le blog de la société EDISON:


FFmpeg libav manual

All Articles