[第2/2部分] FFmpeg和SDL指南或如何编写少于1000行的视频播放器


我们将手册的其余译本翻译成俄语,虽然有些过时了,但并没有失去意义,因为本教程有助于理解使用FFmpeg和SDL库创建视频应用程序的“厨房”。

尽管我们尝试了,但是在如此庞大的文本中翻译的困难是不可避免的报告错误(最好是在私人消息中)-我们在一起将做得更好。

目录


EDISON软件-网络开发
EDISON.

, .

, , Axxon Next SureView Immix.

! ;-)

6:


tutorial06.c
// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

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

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

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

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

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

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

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

#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0

#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20

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

#define VIDEO_PICTURE_QUEUE_SIZE 1

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

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


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

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

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

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

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

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

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

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

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

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

  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;

  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(global_video_state->quit) {
      ret = -1;
      break;
    }

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

void video_display(VideoState *is) {

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

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

void video_refresh_timer(void *userdata) {

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



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

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

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

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

}

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

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

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

  if(is->quit)
    return -1;

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

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

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

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

  if(vp->bmp) {

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

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

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

  double frame_delay;

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

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

  pFrame = av_frame_alloc();

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

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

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

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

int stream_component_open(VideoState *is, int stream_index) {

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

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

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

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


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

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

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

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

int decode_thread(void *arg) {

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

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

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

  global_video_state = is;

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

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

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

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

  // main decode loop

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

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

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

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

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

  screen_mutex = SDL_CreateMutex();

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

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

  schedule_refresh(is, 40);

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

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


现在,我们有一个差不多的播放器甚至可以看电影了,现在让我们收支平衡。上次,我们略微谈到了同步,即声音和视频的同步,以这种顺序进行,而不是相反。我们将使该布局与视频相同:制作一个内部视频时钟以跟踪视频流的远近并与之同步音频。稍后,我们将进一步推广—将音频和视频与外部时钟同步。

视频时钟实现


现在,我们要制作一个与上次音频时钟类似的视频时钟:一个内部值,该值返回当前播放视频的当前时间偏移。您可能会认为,这就像用显示最后一帧的当前PTS更新计时器一样简单。但是,请不要忘记,如果我们降低到毫秒级别,则视频帧之间的时间可能太长。因此,解决方案是跟踪另一个值,即我们最后一帧PTS上设置视频时钟的时间。因此,视频时钟的当前值将为PTS_of_last_frame +(current_time - time_elapsed_since_PTS_value_was_set这个解决方案与我们对get_audio_clock所做的非常相似

因此,在我们成熟的结构中,我们将放置double video_current_ptsint64_t video_current_pts_time时钟将在video_refresh_timer函数中更新

void video_refresh_timer(void *userdata) {

  /* ... */

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

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

不要忘记在stream_component_open中初始化它

is->video_current_pts_time = av_gettime();

现在,我们需要的是某种获取信息的方法:

double get_video_clock(VideoState *is) {
  double delta;

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

从手表中提取


但是,为什么要强迫自己使用视频时钟呢?您可以走得更远,更改我们的视频同步代码,以使音频和视频不会尝试彼此同步。想象一下,如果我们尝试使用FFplay这样的命令行选项来进行操作,那将会是一团糟。因此,让我们抽象一下:我们将创建一个新的包装函数get_master_clock,该函数检查变量av_sync_type,然后调用get_audio_clockget_video_clock或可以使用它的任何其他时钟。我们甚至可以使用计算机时钟,我们称之为get_external_clock

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

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

音频同步


现在最困难的部分是:将音频与视频时钟同步。我们的策略是测量音频的位置,将其与视频时钟进行比较,然后找出需要调整的样本数量,也就是说,是否需要通过删除样本来加快采样速度或通过添加样本来降低采样速度?每次处理获得的每组音频样本时,我们都会

运行syncnize_audio函数,以适当地减少或增加该组音频样本。但是,我们不希望一直保持同步,因为音频处理比处理视频数据包要频繁得多。因此,我们将设置对syncnize_audio函数的连续调用的最小数量在我们做任何事情之前,它们都被认为是不同步的。当然,与上次一样,“不同步”是指音频时钟和视频时钟相差大于同步阈值的量。

所以我们将使用分数系数s,现在,我们得到N不同步的音频样本集。我们不同步的样本数量也可能相差很大,因此我们取每个样本不同步的平均值。例如,第一个调用可能表明我们在40毫秒内未同步,下一个50毫秒,等等。但是我们不会采取简单的方法,因为最新的价值观比之前的价值观更为重要。因此,我们将使用分数系数c,并将差异总结如下:diff_sum = new_diff + diff_sum * c。准备好求平均差时,我们只需计算avg_diff =diff_sum *(1- c)。

这到底是怎么回事?方程式看起来像是一种魔术。好吧,这基本上是使用几何序列作为权重的加权平均值。我不知道是否有这个名称(我什至在Wikipedia上检查过!),但是有关更多信息,这里有一个解释(或这里:weightedmean.txt)。

这是我们的函数:

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

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

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

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

所以我们一切都很好。我们大概知道多少声音与视频或我们用作手表的声音不一致。因此,现在让我们将这段代码放在“减少/扩展缓冲区代码”部分中,以计算需要添加或丢弃的样本数量:

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

请记住,audio_length *(sample_rate * 通道数 * 2)是音频的audio_length秒的采样数因此,我们想要的样本数量将等于我们已经拥有的样本数量,加上或减去与播放声音的时间量相对应的样本数量。我们还将对校正的大小设置一个限制,因为如果我们过多更改缓冲区,对用户来说将太烦人了。

样品数量校正


现在我们需要修复声音。您可能已经注意到,我们的syncnize_audio函数返回一个样本大小,然后告诉我们要发送到流中的字节数。因此,我们只需要将样本大小调整为所需值即可。这样可以减小样本量。但是,如果您需要增加它,我们就不能仅仅增加样本量,因为缓冲区中没有更多数据了!因此,我们必须添加一点。但是到底要添加什么呢?尝试外推音频是愚蠢的,所以让我们只使用已有的音频,将最后一个样本的值添加到缓冲区中即可。

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

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

现在,我们将返回样本大小,并完成此函数。我们现在需要做的就是使用这个:

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

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

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

我们所做的只是插入一个syncnize_audio调用(此外,请务必检查源代码,在该源代码中我们初始化不需要打扰的变量。)

最后,在完成之前:我们需要添加“ if”条件,以确保如果它是主时钟,则不同步视频:

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

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

而且有效!确保检查源文件以初始化所有我不想定义或初始化的变量。然后编译:

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

并且飞行将是正常的。

在上一课中,我们将倒带。






第7课:搜索


完整列表tutorial07.c
// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

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

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

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

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

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

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

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

#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0

#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20

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

#define VIDEO_PICTURE_QUEUE_SIZE 1

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

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


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

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

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

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

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

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

enum {
  AV_SYNC_AUDIO_MASTER,
  AV_SYNC_VIDEO_MASTER,
  AV_SYNC_EXTERNAL_MASTER,
};

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

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

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

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

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

  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;

  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(global_video_state->quit) {
      ret = -1;
      break;
    }

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

void video_display(VideoState *is) {

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

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

void video_refresh_timer(void *userdata) {

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



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

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

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

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

}

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

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

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

  if(is->quit)
    return -1;

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

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

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

  if(vp->bmp) {

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

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

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

  double frame_delay;

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

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

  pFrame = av_frame_alloc();

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

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

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

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

int stream_component_open(VideoState *is, int stream_index) {

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

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

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

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


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

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

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

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

int decode_thread(void *arg) {

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

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

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

  global_video_state = is;

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

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

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

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

  // main decode loop

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

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

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

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

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

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

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

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

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

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

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

  screen_mutex = SDL_CreateMutex();

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

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

  schedule_refresh(is, 40);

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

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

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

}

搜索命令处理


现在,我们将在播放器中添加一些搜索功能,因为当您无法倒带影片时,这确实很烦人。另外,我们将看到使用av_seek_frame函数是多么容易

我们将使键盘上的箭头“向左”和“向右”向前或向后滚动一点,而“向上”和“向下”箭头已经更加重要。 “一点”-将是10秒,“很多”-全部是60秒。因此,我们需要配置主循环,以便它拦截击键事件。但是事实是,当我们击键时,我们无法直接调用av_seek_frame。这必须在我们的主解码循环中decode_thread循环中完成因此,相反,我们将在主结构中添加一些值,其中将包含搜索的新位置和一些搜索标志:

  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

现在,我们需要配置捕获按键的主循环:

  for(;;) {
    double incr, pos;

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

要捕获按键,首先我们查看是否发生SDL_KEYDOWN事件。然后,我们使用event.key.keysym.sym检查接收到哪个密钥。一旦找出了所要寻找的方向,我们便会计算一个新时间,并为新get_master_clock函数的值增加一个增量。然后,我们调用stream_seek函数来设置seek_pos的,等等。将我们的新时间转换为avcodec内部时间戳单位。回想一下,流中的时间戳使用以下公式以帧而不是秒为单位进行度量: = * time_basefps)。默认情况下,avcodec设置为每秒1,000,000帧(因此2秒的位置将具有2,000,000的时间戳)。为什么我们需要转换此值-稍后请参见。

这是我们的stream_seek函数。请注意,如果返回,则设置标志:

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

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

现在,让我们继续进行我们的解码_thread,我们实际上在其中执行搜索。在源文件中,您可以看到我们已将区域标记为“搜索正在进行中”。好吧,我们现在要把它放在那里。

搜索以av_seek_frame函数为中心。此函数将格式上下文,流,时间戳和标志集作为参数。该函数将查找您给它的时间戳。时间戳的单位是您传递给函数time_base。但是,您不需要将其传递到流(通过传递值-1表示)。如果执行此操作,则time_base将位于avcodec内部时间单位中或1000000fps。这就是为什么我们在设置seek_pos时将位置乘以AV_TIME_BASE的原因

但是,有时候,如果为流传递av_seek_frame -1,可能会(很少)遇到某些文件问题,因此我们将选择文件中的第一个流并将其传递给av_seek_frame。不要忘记,我们必须更改时间戳的比例,才能使用新的“坐标系”。

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

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

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

av_rescale_qabc)是一个将时间戳从一个基准缩放到另一个基准的函数。它基本上计算a * b / c,但是此函数派上用场,因为此计算有时会导致溢出。AV_TIME_BASE_Q是一个小数版本AV_TIME_BASE。它们是完全不同的:AV_TIME_BASE * time_in_seconds = avcodec_timestampAV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds(但请注意,AV_TIME_BASE_Q实际上是AVRational对象,因此您需要avcodec中使用特殊的q函数来处理它)。

缓冲液清洗


因此,我们正确设置了搜索,但尚未完成。记住,我们是否配置了一个队列来累积数据包?现在我们处于不同的时间戳,我们需要清除此队列,否则电影中的搜索将无法进行!此外,avcodec具有自己的内部缓冲区,还需要为每个流刷新该缓冲区。

为此,您必须首先编写一个清除包队列的函数。然后,您需要以某种方式指示音频和视频流清除了avcodec内部缓冲区。我们可以通过在清理后在队列中放置一个特殊的数据包来做到这一点,当他们(线程)发现此特殊数据包时,他们将简单地清除其缓冲区。

让我们从重置功能开始。这确实非常简单,所以我只向您展示代码:

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

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

现在,队列已清除,添加我们的“清理包”。但首先,最好定义它并创建它:

AVPacket flush_pkt;

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

现在将此包放入队列:

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

(此代码段延续了上述code_code的代码段。)我们还需要修改packet_queue_put,以免重复用于清洗的特殊程序包:

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

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

然后在音频和视频流中,我们在packet_queue_get之后紧接着将此调用放入avcodec_flush_buffers中

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

上面的代码段与视频流完全相同,只是用“视频”代替了“音频”。

就是这个!我们做到了!编译播放器:

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

并享受不到1000行C语言制作的电影播放器​​!

尽管当然可以添加或改进很​​多东西。






后记


因此,我们有一个能干的球员,但是当然这并不尽如人意。可以修改文件并添加许多有用的东西:

  • 面对现实,这个球员很烂。ffplay.c所基于的版本已完全过时,因此,本教程需要进行彻底修订。如果要使用FFmpeg库进行更重要的项目,强烈建议您将ffplay.c的最新版本作为下一个任务。
  • 我们代码中的错误处理非常糟糕,可以更好地实现。
  • , , , . , paused , , . , , . av_read_play. - , . , , . : , ffplay.c.
  • .
  • . , , , , VOB-.
  • . , .
  • . .
  • , , , , YUV, time_base.
  • .
  • --, ; ffplay.c .


如果您想了解有关FFmpeg的更多信息,那么在这里我们已经考虑了远非一切。下一步是研究多媒体编码。最好从文件output_example.c开始,您可以在FFmpeg发行版中找到该文件。我可以写另一本有关该主题的教科书,但它不可能超越本指南。

UPD。很久以前,我没有更新本文,但与此同时,世界并没有停滞不前。本教程仅需要简单的API更新。基本概念方面的变化很小。这些更新大多数实际上简化了代码。但是,尽管我遍历了所有代码并对其进行了更新,但FFplay仍然优于该玩具播放器。坦率地说,我们承认:在这些课程中,我们编写了一个非常糟糕的电影播放器​​。因此,如果您今天(或将来)想要改进本教程,建议您熟悉FFplay并找出缺少的内容。我认为这主要涉及视频设备的使用,但是很可能我缺少其他明显的东西。与当前的FFplay进行比较也许会导致某些事情的彻底改写-我还没有看过它。

但是我感到非常自豪的是,多年来,即使考虑到人们经常在其他地方搜索代码的事实,我的工作也有所帮助。我非常感谢车里雅夫(Chelyaev),他承担了自八年前编写本专着以来更换所有过时功能的例行工作。

我很高兴希望这些教训对我们有用,而且不会枯燥。如果对本指南有任何建议,错误,投诉,感谢等,请给我发送电子邮件至dranger dog gmail dot com。是的,让我为您的FFmpeg项目提供帮助没有任何意义。类似的字母太多了。






附录1.功能列表


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

打开媒体文件的名称,将格式上下文保存在ptr中指定的地址中

fmt:如果不为NULL,则设置文件格式。
buf_size:缓冲区大小(可选)。
options:AVDictionary填充了AVFormatContext和多路分解器的参数

void avformat_close_input(AVFormatContext **s)

关闭媒体文件。但是,它不会关闭编解码器。

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

创建一个I / O上下文以使用url中指定的资源

s:指向将创建AVIOContext的位置的指针。如果失败,则将指定值设置为NULL。
url:要访问的资源的名称。
标志:控制url中指定的资源的打开
int_cb:用于协议级别使用的中断回调。
options:填充有私有协议参数的字典。当函数返回时,该参数将被销毁并替换为包含未找​​到选项的字典。可能为NULL。

int av_dup_packet(AVPacket *pkt)

当然,这是一个hack:如果尚未分配此软件包,则将其发布在此处。成功返回0,失败返回AVERROR_NOMEM。

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

此功能搜索非显而易见的流信息,例如帧速率。对于无头文件格式(例如MPEG)很有用。建议在打开文件后调用。如果成功,则返回> = 0,如果发生错误,则返回AVERROR_ *。

AVFrame *avcodec_free_frame()

av_frame_free的旧名称。在lavc 55.28.1中更改。

void av_frame_free (AVFrame **frame)

释放框架及其中的任何动态分配的对象,例如,extended_data。

void av_free(void *ptr)

释放使用av_malloc()或av_realloc()分配的内存。您可以使用ptr == NULL调用此函数。建议您改为调用av_freep()。

void av_freep(void *ptr)

释放内存并将指针设置为NULL。内部使用av_free()。

void av_free_packet(AVPacket *pkt)

包装程序包破坏方法(pkt-> destruct)。

int64_t av_gettime()

获取当前时间(以微秒为单位)。

void av_init_packet(AVPacket *pkt)

初始化可选的软件包字段。

void *av_malloc(unsigned int size)

对齐的内存分配字节大小适合所有内存访问(包括向量,如果在CPU上可用)。av_malloc(0)应该返回一个非零的指针。

void *av_mallocz(unsigned int size)

与av_malloc()相同,但将内存初始化为零。

double av_q2d(AVRational a)

AVRational翻倍。

int av_read_frame(AVFormatContext *s, AVPacket *pkt)

返回下一个流帧。信息作为软件包存储在pkt中。

返回的包在下一个av_read_frame()或av_close_input_file()之前一直有效,并且必须使用av_free_packet释放它。对于视频包,仅包含一帧。对于音频,如果每个帧具有已知的固定大小(例如PCM或ADPCM数据),则它包含整数个帧。如果音频帧的大小可变(例如MPEG音频),则它包含一帧。

pkt-> pts,pkt-> dts和pkt->持续时间始终以AVStream.timebase为单位设置为正确的值(并且假定格式无法提供它们)。如果视频格式具有B帧,则pkt-> pts可以为AV_NOPTS_VALUE,因此,如果不对有效载荷进行拆包,最好依靠pkt-> dts。

返回结果:如果一切正常,则返回 0;如果有错误或文件末尾,则返回 <0。

void av_register_all();

在库中注册所有编解码器。

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

返回一个 * BQ / CQ

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

在时间戳上搜索关键帧。

stream_index:如果stream_index为-1,则选择默认流,并且时间戳会自动从AV_TIME_BASE单位转换为特定于流的time_base。
timestamp:以AVStream.time_base为单位测量的时间戳,如果未指定流,则以AV_TIME_BASE为单位测量。
flags:设置有关方向和搜索模式的参数:
AVSEEK_FLAG_ANY:在任何帧中搜索,而不仅仅是关键帧。
AVSEEK_FLAG_BACKWARD:沿相反方向搜索。
AVSEEK_FLAG_BYTE:基于字节的位置搜索。

AVFrame *avcodec_alloc_frame()

av_frame_alloc的旧名称。在lavc 55.28.1中更改。

AVFrame *av_frame_alloc()

选择一个AVFrame并将其初始化。可以使用av_frame_free()释放。

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

将音频帧从avpkt解码到帧。 avcodec_decode_audio4()函数从AVPacket解码音频文件。对于其解码,使用音频编解码器,该音频编解码器使用avcodec_open2()与avctx关联。所得的解码帧存储在指定的AVFrame中。如果帧已解压缩,则会将got_frame_ptr设置为1。

警告:输入缓冲区avpkt->数据必须比实际读取的字节大FF_INPUT_BUFFER_PADDING_SIZE,因为某些优化的位流读取器一次读取32或64位,并且最多可以读取结束。

avctx:编解码器上下文。
frame:目标帧。
got_frame_ptr:target int,如果拆包框架则将设置此参数。
病毒库:包含音频的AVPacket。

返回结果:如果返回错误,则返回负值;否则,返回从输入AVPacket使用的字节数。

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

将视频帧从buf解码为图像。 avcodec_decode_video2()函数从大小为buf_size的输入缓冲区解码视频帧。对于其解码,使用视频编解码器,该视频编解码器使用avcodec_open2()与avctx关联。所得的解码帧保存在图片中。

警告:适用于avcodec_decode_audio4的对齐示例和缓冲区问题也适用于此函数。

avctx:编解码器上下文。
图片:将保存解码视频的AVFrame。
frameFinished:如果没有框架可以解包,则为零,否则不等于零。
avpkt:包含输入缓冲区的输入AVPacket。您可以使用av_init_packet()创建这样的程序包,然后,在具有指定的数据和大小的情况下,某些解码器可能还需要其他字段,例如标志和AV_PKT_FLAG_KEY。所有解码器均设计为使用尽可能少的字段。

返回结果:错误时,将返回负值,否则将使用字节数;如果无法解压缩任何帧,则使用零。

int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)

一种从AVFrame对象获取best_effort_timestamp的简单访问方法。

AVCodec *avcodec_find_decoder(enum CodecID id)

搜索带有CodecID的解码器。错误返回NULL。在使用codecCtx-> codec_id从AVFormatContext中的流中获取所需的AVCodecContext之后,应调用它。

void avcodec_flush_buffers(AVCodecContetx *avctx)

缓冲液冲洗。在搜索或切换到另一个流时调用。

AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)

分配AVCodecContext并将其字段设置为默认值。

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

将源AVCodecContext的设置复制到目标AVCodecContext。目的编解码器的结果上下文将被关闭,即 您必须先调用avcodec_open2(),然后才能使用此AVCodecContext解码/编码视频/音频数据。

dest:必须使用avcodec_alloc_context3(NULL)初始化,否则将不会初始化。

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

初始化avctx以使用codec中指定的编解码器应在avcodec_find_decoder之后使用。成功返回零,错误返回负值。

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

使用ptr buffer pix_fmt format 以及指定的宽度和高度设置图片所指向的结构返回图像数据的大小(以字节为单位)。

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

计算给定宽度,高度和图像格式的图像需要多少字节。

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

返回在sws_scale中使用的SwsContext。

srcWsrcHsrcFormat:所需像素的宽度,高度和格式。
dstWdstHdstFormat:最终像素的宽度,高度和格式。
flags:要使用的缩放方法。
可以使用以下选项:SWS_FAST_BILINEAR,SWS_BILINEAR,SWS_BICUBIC,SWS_X,SWS_POINT,SWS_AREA,SWS_BICUBLIN,SWS_GAUSS,SWS_SINC,SWS_LANCZOS,SWS_SPLINE。
其他标志包括CPU功能标志:SWS_CPU_CAPS_MMX,SWS_CPU_CAPS_MMX2,SWS_CPU_CAPS_3DNOW,SWS_CPU_CAPS_ALTIVEC。
其他标志包括(当前尚未完全实现)SWS_FULL_CHR_H_INT,SWS_FULL_CHR_H_INP和SWS_DIRECT_BGR。
最后,还有SWS_ACCURATE_RND,对于初学者来说可能是最有用的SWS_PRINT_INFO。
我不知道他们大多数人做什么。也许写信给我?
srcFilterdstFilter:源和目标的SwsFilter。 SwsFilter启用颜色/亮度过滤。默认值为NULL。
param:应该是指向带有系数的int [2]缓冲区的指针。没有记录。它似乎用于略微修改标准缩放算法。默认值为NULL。仅适用于专家!

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

根据SwsContext * c中的设置 缩放src中的数据srcStridedstStride是源行和目标行的大小。


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

添加一个在指定的毫秒数后运行的回调函数。回调函数通过SDL_AddTimer调用传递当前计时器间隔和用户提供的参数,并返回下一个计时器间隔。 (如果回调的返回值与传递的值匹配,则计时器继续以相同的速度工作。)如果回调的返回值为0,则取消计时器。
取消当前计时器的另一种方法是使用计时器标识符(从SDL_AddTimer返回)调用SDL_RemoveTimer。

计时器回调函数可能在与主程序不同的线程上执行,因此不应从自身调用任何函数。但是,您始终可以调用SDL_PushEvent。

计时器的详细程度取决于平台,但是您应该期望它至少为10 ms,因为这是最常见的值。这意味着,如果您请求一个16 ms的计时器,则在卸载的系统上,回调将在大约20 ms后开始。如果您需要设置一个标志,以每秒30帧的速度(每33毫秒)发信号通知更新帧,则可以将计时器设置为30毫秒(请参见下面的示例)。如果使用此功能,则需要将SDL_INIT_TIMER传递给SDL_Init。

返回添加的计时器的标识符值,如果发生错误,则返回NULL。

回调格式:
Uint32 callback ( Uint32, void * param)


int SDL_CondSignal(SDL_cond *cond)

重新启动等待cond条件变量的线程之一成功返回0,错误返回-1。

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

解锁提供的互斥锁,然后等待另一个线程调用cond条件变量的SDL_CondSignal或SDL_CondBroadcast,然后重新锁定该互斥锁。进入此功能之前,必须锁定互斥锁。接收到信号时返回0,否则返回-1。

SDL_cond *SDL_CreateCond(void);

创建一个条件变量。

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

SDL_CreateThread创建一个新的执行线程,该线程共享其父代,信号处理程序,文件描述符等的整个全局内存。然后它运行fn函数,并向其传递空指针数据。当fn返回值时,线程终止。

void SDL_Delay (Uint32 );

等待指定的毫秒数。 SDL_Delay将至少等待指定的时间,但由于操作系统计划,可能会更长。
注意:期望延迟粒度至少为10 ms。一些平台的度量标准较短,但这是最常见的选择。

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

SDL_CreateYUVOverlay为提供的显示创建具有指定宽度,高度和格式的YUV覆盖(有关可用格式的列表,请参见SDL_Overlay数据结构)。返回SDL_Overlay。

display实际上应该是从SDL_SetVideoMode派生的表面,否则默认情况下此功能将起作用。

术语“覆盖”是不正确的,因为如果覆盖不是在硬件中创建的,则当显示覆盖时,覆盖显示区域下方的显示表面的内容将被覆盖。

int SDL_LockYUVOverlay(SDL_Overlay *overlay)

SDL_LockYUVOverlay阻止覆盖以直接访问像素数据。成功返回0,错误返回-1。

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)

解锁先前锁定的叠加层。叠加层必须先解锁,然后才能显示。

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)

将叠加层放置在创建时指定的表面上。dstrect SDL_Rect结构定义了目的地的位置和大小。如果dstrect或多或少是叠加层,则叠加层将被缩放,这已针对2倍缩放进行了优化。如果成功,则返回0。

void SDL_FreeYUVOverlay(SDL_Overlay *overlay)

释放由SDL_CreateYUVOverlay创建的叠加层。

int SDL_Init(Uint32 flags);

初始化SDL。应该在所有其他SDL函数之前调用此函数。flags参数指定要初始化SDL的哪些部分。

SDL_INIT_TIMER-初始化计时器子系统。
SDL_INIT_AUDIO-初始化音频子系统。
SDL_INIT_VIDEO-初始化视频子系统。
SDL_INIT_CDROM-初始化CD-ROM子系统。
SDL_INIT_JOYSTICK-初始化操纵杆子系统。
SDL_INIT_EVERYTHING-初始化以上所有内容。
SDL_INIT_NOPARACHUTE-不允许SDL捕获致命错误。
SDL_INIT_EVENTTHREAD-在单独的线程中启动事件管理器。

错误时返回-1,成功时返回0。您可以通过调用SDL_GetError获得扩展的错误消息。错误的典型原因是使用特定的显示器而没有相应的子系统支持,例如,在与设备一起使用帧缓冲区时缺少鼠标驱动程序。在这种情况下,您可以在不使用鼠标的情况下编译SDL,或者在启动应用程序之前设置环境变量“ SDL_NOMOUSE = 1”。

SDL_mutex *SDL_CreateMutex(void);

创建一个新的未锁定互斥锁。

int SDL_LockMutex(SDL_mutex *mutex)

SDL_LockMutex是SDL_mutexP的别名。它会阻止以前使用SDL_CreateMutex创建的互斥锁。如果互斥锁已经被另一个线程阻止,则SDL_mutexP不会返回值,直到被它阻止的线程将其解锁(使用SDL_mutexV)。当再次调用互斥锁时,必须多次调用SDL_mutexV(又名SDL_UnlockMutex),才能使互斥锁返回解锁状态。成功返回0,错误返回-1。

int SDL_UnlockMutex(SDL_Mutex *mutex)

互斥锁解锁。

int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

此函数使用所需的参数打开音频单元,如果成功,则返回0,将实际的硬件参数放置在最终指向的结构中。如果接收到NULL值,则将确保传递给回调函数的音频数据具有所需的格式,并且在必要时将自动转换为硬件音频格式。如果无法打开音频设备或无法配置音频流,则此函数返回-1。

要打开音频设备,您需要创建所需的SDL_AudioSpec。然后,您需要用所需的音频规范填充此结构。

所需->频率:所需的声音频率,以每秒采样数为单位。
所需->格式:所需的音频格式(请参阅SDL_AudioSpec)。
所需频道:必需的声道(1个单声道,2个立体声,4个环绕声,6个具有居中和LFE的环绕声)。
所需->样本样本中所需的音频缓冲区大小。该数字必须是2的幂,并且可以由音频驱动程序调整为更适合硬件的值。最佳值的范围为512到8192(含),具体取决于应用程序和处理器速度。较小的值会导致更快的响应时间,但是如果应用程序执行繁重的处理并且无法及时填充音频缓冲区,则可能导致性能下降。立体声样本由LR顺序的左右声道组成。请注意,使用以下公式,采样数与时间直接相关:ms =(样品 * 1000)/ 频率
required-> callback:必须设置为在音频单元准备好接收其他数据时将调用的函数。发送指向音频缓冲区的指针和音频缓冲区的字节长度。该函数通常在单独的线程中执行,因此有必要通过在代码中调用SDL_LockAudio和SDL_UnlockAudio来保护其访问的数据结构。回调原型为void回调void * userdataUint8 * streamint lenuserdata-存储在用户数据字段SDL_AudioSpec中的指针。是指向要填充信息的音频缓冲区的指针,len是音频缓冲区的长度(以字节为单位)。
required-> userdata:此指针作为第一个参数传递给回调函数。

SDL_OpenAudio从传递给函数的所需SDL_AudioSpec结构中读取这些字段,并尝试查找与您的需求相匹配的音频配置。如上所述,如果结果参数为NULL,则在播放期间SDL从所需的声音设置转换为设备设置。

如果返回NULL,则必需的SDL_AudioSpec是您的工作规范,否则生成的SDL_AudioSpec成为工作规范,可以删除所需的规范。在构建SDL_AudioCVT时,使用工作规范中的数据将下载的数据转换为设备格式。

SDL_OpenAudio计算所需规范和最终规范的大小和静默字段。 size字段以字节为单位存储音频缓冲区的总大小,而silent存储用于表示音频缓冲区中的静音的值

声音设备打开时会开始播放静音,应在准备好调用音频回调函数时通过调用SDL_PauseAudio(0)将其打开以进行播放。由于音频驱动程序可以更改请求的音频缓冲区大小,因此在打开音频设备后必须选择任何本地混音器缓冲区。

void SDL_PauseAudio(int pause_on)

此功能暂停并停止音频回调处理。打开音频设备后,必须以pause_on = 0调用它才能开始播放声音。这样,您可以在打开音频单元后安全地初始化回调函数的数据。暂停期间,静音会记录在音频设备上。

int SDL_PushEvent(SDL_Event *event)

一个事件队列,实际上用作双向通信通道。不仅可以从队列中读取事件,而且用户还可以将自己的事件放入队列中。事件是指向您要排队的事件的结构的指针。该事件被复制到队列,并且调用者可以管理返回SDL_PushEvent之后指向的内存。该函数是面向线程的,可以从其他线程安全地调用。如果成功,则返回0;如果无法调度事件,则返回-1。

int SDL_WaitEvent(SDL_Event *event)

无限期等待下一个可用事件,如果在等待事件时发生错误,则返回0,否则返回1。如果事件不为NULL,则将下一个事件从队列中删除并存储在此区域中。

void SDL_Quit()

禁用所有SDL子系统并释放分配给它的资源。退出前应始终调用它。

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

具有指定的宽度,高度和像素位的视频模式设置。从SDL 1.2.10开始,如果width和height为0,它将使用当前视频模式(如果未设置该模式,则使用桌面模式)的宽度和高度。如果bitsperpixel为0,则将其视为每个像素的当前显示位。flags参数与SDL_Surface结构的flags字段相同。或以下值的组合:

SDL_SWSURFACE-在系统内存中创建视频界面。
SDL_HWSURFACE-在视频内存中创建视频表面。
SDL_ASYNCBLIT-启用对显示表面的异步更新。这通常会减慢单处理器计算机上的工作,但可以提高SMP系统中的速度。
SDL_ANYFORMAT-通常,如果具有要求的每像素位数(bpp-每像素位数)的视频表面不可用,则SDL将模拟带有阴影表面的视频。传递SDL_ANYFORMAT可以防止这种情况,并强制SDL使用视频的表面,而不考虑其深度(以像素为单位)。
SDL_HWPALETTE-提供对调色板的SDL独占访问。没有此标志,您将无法始终使用SDL_SetColors或SDL_SetPalette获得所需的颜色。
SDL_DOUBLEBUF-启用硬件双重缓冲;仅对SDL_HWSURFACE有效。调用SDL_Flip将反转缓冲区并刷新屏幕。所有绘图将在当前未显示的表面上进行。如果无法启用双缓冲,则SDL_Flip将仅全屏执行SDL_UpdateRect。
SDL_FULLSCREEN SDL-尝试使用全屏模式。如果由于某种原因无法更改硬件分辨率,则将使用下一个更高的分辨率,并且显示窗口将以黑色背景为中心。
SDL_OPENGL-创建OpenGL渲染上下文。假定已预先设置具有SDL_GL_SetAttribute的OpenGL视频属性。
SDL_OPENGLBLIT-如上所述,创建OpenGL渲染上下文,但允许正常的blitting操作。屏幕表面(2D)可以具有Alpha通道,应使用SDL_UpdateRects更新屏幕表面的变化。注意。该选项仅出于兼容性目的而保存,在以后的版本中将被删除。不建议在新代码中使用。
SDL_RESIZABL-创建可调整大小的窗口。当用户更改窗口大小时,将引发SDL_VIDEORESIZE事件,并且可以使用新大小再次调用SDL_SetVideoMode。
SDL_NOFRAME如果可能,SDL_NOFRAME强制SDL创建一个没有标题或带边框的窗口。此标志自动设置为全屏模式。
注意。无论满足哪些SDL_SetVideoMode标志,都将在返回的表面的flags元素中进行设置。
注意。位像素24使用每个像素3个字节的压缩表示。对于比较常见的每像素4字节模式,请使用32位像素,奇怪的是,15和16都将要求每像素2字节模式,但是像素格式不同。
注意。如果您计划执行单独的像素操作或使用Alpha通道拖动表面,并且需要较高的帧速率,请使用SDL_SWSURFACE。当您使用硬件表面(SDL_HWSURFACE)时,SDL在您锁定表面时将其从视频内存复制到系统内存,反之亦然。这可能会导致性能显着下降。 (请记住,您可以查询硬件表面,但仍然可以获得软件表面。许多平台仅在使用SDL_FULLSCREEN时才可以提供硬件表面。)SDL_HWSURFACE最适合当您要绘制的表面也可以存储在视频内存中时使用。
注意。如果要在创建窗口表面时控制屏幕上的位置,可以通过设置环境变量“ SDL_VIDEO_CENTERED = center”或“ SDL_VIDEO_WINDOW_POS = x,y”来实现。您可以通过SDL_putenv安装它们。

返回值:帧缓冲区表面或失败时为NULL。返回的表面由SDL_Quit释放,并且调用者不得释放。
注意。该规则包括对SDL_SetVideoMode的连续调用(即调整大小)-现有表面将自动释放。






附录2中的数据结构 →交通



AVCodecContext

流中来自AVStream->编解码器的有关编解码器的所有信息。一些重要的属性:

AVRational time_base:每秒的帧数
int sample_rate:每秒的采样数
int 通道通道数在此处

查看完整列表(非常令人印象深刻)网络档案,因为原始链接已经不存在)。许多参数主要用于编码,而不用于解码。

AVFormatContext

数据字段:

常量AVClass * av_class
AVInputFormat * iformat
AVOutputFormat * oformat
无效 * priv_data
ByteIOContext PB:用于低级别文件操作。
unsigned int nb_streams:文件中的线程数。
AVStream *流[MAX_STREAMS]:每个流的数据都存储在此处。
char文件名[1024]:但是如果没有它,该怎么办(在原始文件中-duh)。

文件信息:
int64_t 时间戳
字符标题 [512]:
字符作者 [512]:
字符版权 [512]:
字符注释 [512]:
字符专辑 [512]:
整数年份
整数轨道
字符风格 [32]:

整数ctx_flags
可能的值是AVFMT_NOFILE,AVFMT_NEEDNUMBER,AVFMT_SHOW_IDS,AVFMT_RAWPICTURE, AVFMT_GLOBALHEADER,AVFMT_NOTIMESTAMPS,AVFMT_GENERIC_INDEX
AVPacketList * packet_buffer:仅当数据包已经缓冲但尚未解码时才需要此缓冲区,例如,以在mpeg流中接收编解码器参数。
int64_tstart_time:解码时:组件的第一帧的位置,以秒为单位,AV_TIME_BASE。永远不要直接设置此值:它是从AVStream的值推断出来的。
int64_t持续时间:解码:流的持续时间,以AV_TIME_BASE的分数表示。永远不要直接设置此值:它是从AVStream的值推断出来的。
int64_t file_size:文件总大小,如果未知,则为0。
int bit_rate:解码:流的总比特率,以bit / s为单位,如果不可用,则为0。如果file_size和ffmpeg中已知的持续时间可以自动计算,则永远不要直接设置它。
AVStream * cur_st
const uint8_t * cur_ptr
int cur_len
AVPacket cur_pkt
int64_t data_offset
int index_built:第一个数据包的偏移量。
int mux_rate
int packet_size
int预加载
int max_delay
int loop_output:支持的格式的输出循环数。
int标志
int loop_input
无符号int probesize:解码:样本数据大小;不用于编码。
int max_analyze_duration:必须以av_find_stream_info()分析输入数据的最大持续时间,以AV_TIME_BASE为单位
const uint8_t * key
int keylen

AVIOContext

I / O上下文用于访问资源。

const AVClass * av_class:私有设置的类。
unsigned char * buffer缓冲区的开始。
int buffer_size:最大缓冲区大小。
unsigned char * buf_ptr:缓冲区中的当前位置。
unsigned char * buf_end:例如,如果读取函数返回的数据少于请求的数据,则数据可能小于buffer + buffer_size。
void * opaque:传递给读取/写入/搜索/ ...的私有指针
int(* read_packet)(void *不透明,uint8_t * buf,int buf_size)
int(* write_packet )(void *不透明,uint8_t * buf,int buf_size )
int64_t(*寻道)(无效*不透明,int64_t偏移量,int whence)
int64_t pos:当前缓冲区文件中的位置。
int must_flush:如果下一次搜索应重置,则为 true。
int eof_reached:如果到达文件末尾,则为 true。
int write_flag:如果可以写入则为 true。
int max_packet_size
无符号长校验和
无符号字符* checksum_ptr
无符号长(* update_checksum)(无符号长校验和,const uint8_t * buf,无符号int大小)
int错误:包含错误代码;如果未发生错误,则为0。
int(* read_pause)(无效*不透明,int暂停):例如,暂停或恢复网络流协议的回放。
int64_t(* read_seek)(无效*不透明,int stream_index,int64_t时间戳,int标志):使用指定的stream_index索引在流中搜索指定的时间戳。
int seekable:当无法搜索流时,AVIO_SEEKABLE_或0标志的组合。
int64_t maxsize:用于限制选择的最大文件大小。此字段是libavformat的内部字段,禁止从外部对其进行访问。
int direct:avio_read和avio_write应该在可能的情况下直接执行,并且不要通过缓冲区,并且avio_seek将始终直接调用主搜索函数。
int64_t bytes_read:字节读取统计信息此字段在libavformat内部,并且拒绝外部访问。
int seek_count:搜索统计信息。此字段是libavformat的内部字段,禁止从外部对其进行访问。
int writeout_count:写入统计信息。此字段是libavformat的内部字段,禁止从外部对其进行访问。
int orig_buffer_size:检查并提供返回值以重置缓冲区大小后在内部使用的原始缓冲区大小。此字段是libavformat的内部字段,禁止从外部对其进行访问。

AVDictionary

用于将参数传递给ffmpeg。

int count
AVDictionaryEntry * elems

AVDictionaryEntry

用于将字典条目存储在AVDictionary中。

char * ket
char *值

AVFrame

此结构取决于编解码器的类型,因此是动态确定的。但是,此结构有一些共同的属性和方法:

uint8_t * data [4]
int linesize [4]:信息步幅。
uint8_t * base [4]
int key_frame
int pict_type
int64_t pts:这些不是您在解码时期望的pts。
int coded_picture_number
int display_picture_number
int质量
int年龄
int参考
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 *不透明:用户数据
uint64_t错误[4]
int类型
int repeat_pict:指示您重复指定次数的图像。
INT qscale_type
INT interlaced_frame
INT的top_field_first
AVPanScan * pan_scan
INT palette_has_changed
INT buffer_hints
短* dct_coeff
中int8_t * ref_index [2]

AVPacket

存储原始数据包数据的结构。此数据必须传输到avcodec_decode_audio2或avcodec_decode_video才能接收帧。

int64_t pts:以time_base为单位的演示时间戳记。
int64_t dts:解压缩的时间戳记,以time_base为单位。
uint8_t * data:原始数据。
int size:数据大小。
int stream_index:基于AVFormatContext中的数量,AVPacket来自的流。
int标志:如果数据包是关键帧,则设置PKT_FLAG_KEY。
int duration:演示的持续时间,以time_base为单位(如果不可用,则为0)
void(* destruct)(struct AVPacket *):此包的资源释放功能(默认情况下为av_destruct_packet)。
void * priv
int64_t pos:流中的字节位置,如果未知,则为-1。

AVPacketList

软件包的简单链接列表。

AVPacket pkt
AVPacketList *下一个

AVPicture

此结构与前两个AVFrame数据元素完全相同,因此经常将其丢弃。常用于SWS功能。

uint8_t *数据[4]
int行大小[4]:字符串中的字节数。

AVRational

表示有理数的简单结构。

int num:分子。
int den:分母。

AVStream

流的结构。您可能最常在编解码器中使用此信息。

int索引
int id
AVCodecContext *编解码器
AVRational r_frame_rate
void *
priv_dataint64_t codec_info_duration
int codec_info_nb_frames
AVFrac pts
AVRational time_base
int pts_wrap_bits
int stream_copy可以
丢弃在多路分解中。
浮质量
的int64_t START_TIME
的int64_t持续时间
字符语言[4]
INT need_pa​​rsing:1 - >需要完全解析,2 -仅>解析报头,而无需重新包装
AVCodecParserContext *解析器
的int64_t cur_dts
INT last_IP_duration
的int64_t last_IP_pts
AVIndexEntry * index_entries
INT nb_index_entries
一个unsigned int index_entries_allocated_size
int64_t nb_frames:此流中的帧数(如果已知)或0
int64_t pts_buffer [MAX_REORDER_DELAY +1]

ByteIOContext

一种存储有关电影文件的低级信息的结构。

unsigned char *缓冲区
int buffer_size
unsigned char * buf_ptr
unsigned char * buf_end
void *不透明
int(* read_packet)(void *不透明,uint8_t * buf,int buf_size)
int(* write_packet )(void *不透明, uint8_t * buf,int buf_size)
offset_t(* 搜寻)(void *不透明,offset_t offset,int whence)
offset_t pos
int must_flush
int eof_reached
int write_flag
int is_streamed
int max_packet_size
unsigned long校验和
unsigned char * checksum_ptr
unsigned long(* update_checksum)(unsigned long校验和:
const uint8_t * buf,unsigned int size)

int error:包含错误代码;如果未发生错误,则为0。

SDL_AudioSpec

用于描述某些音频数据的格式。

freq:每秒样本中的声音频率。
格式:音频数据格式。
声道声道数:1-单声道,2-立体声,4个环绕声,6个具有居中和LFE环绕声的
无声:声音缓冲器的无声值(计算得出)。
样本样本中音频缓冲区的大小。
size:音频缓冲区的大小(以字节为单位)(已计算)。
callback(..):回调函数,用于填充音频缓冲区。
userdata:指向传递给回调函数的用户数据的指针。

以下格式值

有效AUDIO_U8-8位无符号样本。
AUDIO_S8-带符号的8位样本。
AUDIO_U16或AUDIO_U16LSB-并非所有硬件都支持(无符号16位低字节顺序)。
AUDIO_S16或AUDIO_S16LS-并非所有硬件都支持(旧位顺序为16位)
AUDIO_U16MSB-并非所有硬件都支持(无符号16位big-endian)。
AUDIO_S16MS-并非所有硬件都支持(16位高字节顺序)。
AUDIO_U16SYS:AUDIO_U16LSB或AUDIO_U16MSB-取决于硬件处理器。
AUDIO_S16SYS:AUDIO_S16LSB或AUDIO_S16MSB-取决于硬件处理器。

SDL_Event

事件的基本结构。

type:事件类型。
active:激活事件(请参见SDL_ActiveEvent)。
key:键盘事件(请参见SDL_KeyboardEvent)。
motion:鼠标移动事件(请参见SDL_MouseMotionEvent)。
button:鼠标单击事件(请参见SDL_MouseButtonEvent)。
jaxis:游戏杆轴移动事件(请参见SDL_JoyAxisEvent)。
jball:操纵杆轨迹球运动事件(请参见SDL_JoyBallEvent)。
jhat:操纵杆标头移动事件(请参见SDL_JoyHatEvent)。
jbutton:按下操纵杆按钮的事件(请参见SDL_JoyButtonEvent)。
调整大小:应用程序窗口调整大小事件(请参见SDL_ResizeEvent)。
暴露:应用程序窗口打开事件(请参见SDL_ExposeEvent)。
quit:应用程序退出请求事件(请参见SDL_QuitEvent)。
user:用户事件(请参见SDL_UserEvent)。
syswm:未定义的窗口管理器事件(请参见SDL_SysWMEvent)。

这是事件的类型。有关更多信息,请参见SDL文档

。SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN / UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN / UP
SDL_MouseButtonEvent SDL_JOYAXISMOTION
SDL_JoyAxisEvent SDL_JOYBALLMOTION
SDL_JoyHatEvent SDL_JOYHATMOTION
SDL_JOYBUTTONDOWN / UP SDL_JoyButtonEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_Quit SDL_QuitEvent
SDL_USEREVENT SDL_UserEvent
SDL_SYSWMEMENT SDL

SDL_Overlay

YUV叠加层。

格式:叠加格式(请参见下文)。
w,h:覆盖层的宽度/高度。
planes:要覆盖的计划数。通常为1或3。
间距:一组缩进,每个计划一个。缩进是字符串的长度(以字节为单位)。
pixel:每个计划的数据指针数组。使用这些指针之前,必须锁定覆盖图。
hw_overlay:如果覆盖是硬件加速的,则设置为1。

SDL_Rect

矩形区域。

Sint16 x,y:矩形左上角的位置。
Uint16 w,h:矩形的宽度和高度。

SDL_Rect定义像素的矩形区域。 SDL_BlitSurface使用它来识别斑点和其他一些视频功能的区域。

SDL_Surface

外侧(表面)的图形结构。

Uint32标志:外部存储器的标志。仅用于阅读。
SDL_PixelFormat *格式:只读。
int w,h:宽度和高度。仅用于阅读。
Uint16音高:步进。仅用于阅读。
*像素:指向实际像素数据的指针。仅用于录制。
SDL_Rect clip_rect:剪辑的矩形外部。仅用于阅读。
int refcount:用于分配内存。主要用于阅读。
此结构还包含此处未显示的私有字段。

SDL_Surface表示可以绘制的“图形”内存区域。使用SDL_SetVideoMode和SDL_GetVideoSurface将视频缓冲区帧作为SDL_Surface返回。字段w和h是代表像素的表面宽度和高度的值。像素字段是指向实际像素数据的指针。注意:在访问此字段之前,必须锁定表面(通过SDL_LockSurface)。 clip_rect字段是SDL_SetClipRect设置的裁剪矩形。

标志字段支持以下OR值:

SDL_SWSURFACE-外部存储在系统内存中。
SDL_HWSURFACE-外部存储在视频存储器中。
SDL_ASYNCBLIT-如果可能,外部使用异步眩光。
SDL_ANYFORMAT-允许任何像素格式(显示表面)。
SDL_HWPALETTE-表面具有专用的调色板。
SDL_DOUBLEBUF-双缓冲表面(显示表面)。
SDL_FULLSCREEN-全屏表面(显示表面)。
SDL_OPENGL-表面具有OpenGL上下文(显示表面)。
SDL_OPENGLBLIT-曲面支持对OpenGL进行着色处理(显示曲面)。注意。此选项仅用于兼容性,不建议用于新代码。
SDL_RESIZABLE-可以调整表面(显示表面)的大小。
SDL_HWACCEL-表面钝化使用硬件加速。
SDL_SRCCOLORKEY-肤浅使用颜色涂抹。
SDL_RLEACCEL-使用RLE可以加速颜色模糊。
SDL_SRCALPHA-Surface Blyth使用Alpha混合。
SDL_PREALLOC-表面使用预分配的内存。

SDL_Thread

该结构是系统独立的,您可能不需要使用它。有关更多信息,请参见源代码中的src / thread / sdl_thread_c.h。

SDL_cond

该结构是系统独立的,您可能不需要使用它。有关更多信息,请参见源代码中的src / thread / <system> /SDL_syscond.c。

SDL_mutex

该结构是系统独立的,您可能不需要使用它。有关更多信息,请参见源代码中的src / thread / <system> /SDL_sysmutex.c。






链接


FFmpeg和SDL教程或如何编写少于1000行的视频播放器

FFmpegSDL FFmpeg主页SDL主页











另请参阅EDISON公司的博客:


FFmpeg libav手册

All Articles