FFMpeg 提取音频播放器总结

fmms 13年前
     <p><span style="color:#000000;"><span style="font-family:Helvetica,Tahoma,Arial,sans-serif;">ffmpeg提取音频播放器总结; <br /> 一:简介 <br /> 从编写音频播放器代码到完成播放器编写,测试,整整5天的时间,这时间还不算之前对 ffmpeg熟悉的时间,可以说是历经千辛万苦,终于搞出来了,虽然最终效果还不是很理想,但是已经可以很流畅的播放某些歌曲了,说是某些歌曲,是因为还有些歌曲播放效果不是很好,有些许杂音,至于那些歌曲能够顺利播放,那些不能够,我现在也摸不准是什么原因导致的,有待进一步钻研,等啥时候调好了,就用自己的这个播放器听歌曲了,嘿嘿; <br /> a:插播:) <br /> /**************/ <br /> 这一部分属于插播内容,就不用看了; <br /> tv视频播放; <br /> 采用img_convert时,是转换成24RGB快呢,还是32RGB快呢?可能前者快吧;似乎用qimage的话只能转换成32RGB了;因为它只有三种颜色深度1-p, 8-p, 32-p所以,只能选择32-p了; <br /> <br /> 下面是AVFrame的结构,具体可以看这里: <br /> http://cekirdek.pardus.org.tr/~ismail/ffmpeg-docs/avcodec_8h-source.html#l00424 <br /> 就是一个宏定义,咳。。。 <br /> /**************/ <br /> 二:音频播放器原理 <br /> 音频播放器过程如下所示: <br /> 打开文件--分析文件格式--打开对应解码器--读取一音频帧--解码音频帧--音频数据写入音频设备--循环读取音频帧--再解码。。。如此循环下去; <br /> 整个播放器实现原理详细说明为,采用ffmpeg提供的API函数先用av_open_input_file打开音频文件,分析文件得到格式信息,然后识别格式,并查找对应的解码器,再得到针对此音频文件的解码器之后,用av_read_frame从音频文件中读取一帧,然后对其用 avcodec_decode_audio函数进行解码,在将解码之后的PCM音频数据直接写到audio设备(/dev/dsp)上,根据linux音频设备的原理,此时你就应该听到悦耳的歌声了; <br /> 三:重点要点说明 <br /> 在这个过程当中有几处需要特别注意,下面详细说明一下: <br /> 1、不同音频文件格式对音频压缩率不同,导致对于同一个音频包,你解压出来的音频数据大小也是不一样的,这一点无需惊奇,但是对于这些解压出来的音频数据,一定要保证全部写到声卡当中去,这样才能够作为你能听到悦耳歌声的基础,这里留意一下,这只是一个基础,要想完全实现好此播放器,下一点更是不可或缺的;我之前之所以在调试时总是听到声音很杂乱,或者带有金属声,就是因为声音没有全部写到音频设备中去,当然,可能或多或少也有一些写音频数据的太快的原故; <br /> 2、在确认了解码后的数据是完整的之后,可以将数据写入到音频设备当中了(/dev/dsp),这里很关键的一点就是要对音频设备进行设置,否则你也听不到你想听到的声音:( <br /> 对音频设备的设置主要是四个方面,这不代表其他方面不设置哦: <br /> 设置采样率(有关音频采样率,在我blog前面的文章当中有说明,一般有44100hz,48000hz,22050?不记得了,你查看我blog中前面的文章吧,嘿嘿): <br /> ioctl (fd, SNDCTL_DSP_SPEED, &(pCodecCtx->sample_rate)); <br /> 设置音频声道数(这个很好理解,一般都是立体声了) <br /> </span></span></p>    <pre class="brush:cpp; toolbar: true; auto-links: false;">// set channels;      i = pCodecCtx->channels;      #ifdef AUDIO_DEBUG      printf ("pCodecCtx->channels:%d\n", pCodecCtx->channels);      #endif      if ((ioctl (fd, SNDCTL_DSP_CHANNELS, &i)) == -1)      {          fprintf (stderr, "Set Audio Channels %d failed:%s\n", i,               strerror (errno));          return (-1);      } </pre>    <br /> 这里需要说明的一点是,如果是立体声,则此处i应该等于2,而不是1,网上很多文章这里都说明的不正确,我之前就一直以为立体声为1,单声道为0,总是不出声音,后来一狠心,改为2,尽然ok,faint;     <br /> 结论:网上的东西啊,不可全信之。。。。。。。。。。。。。。。。。。。。。。。。。。。     <br /> 设置量化位数(这个量化位数是指对声音的振幅进行采样的位数,以前一般是8位,现在以16位居多,更高位数对于普通用户用不着,只能在专业音乐中才有价值)     <br /> i = AFMT_S16_LE;        (16位,小端存储,也即intel的倒序数据存储)     <br /> ioctl (fd, SNDCTL_DSP_SETFMT, &i);     <br /> 设置音频驱动级缓存     <br /> i = (0x0004 << 16) + 0x000b;        // four 2kb buffer;你看着对应改就行了(这里是四个2kb缓存)     <br /> ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &i);     <br /> 这里也可以不设置,用系统默认自定义也可;     <br />    <br /> 另外有一个疑问也顺带解决了:     <br /> Q:播放音频和pts有关系么?需要他来调整播放的快慢么?就像视频那样?     <br /> A:基本没有关系,至少我目前没有用到这个咚咚,pts应该实在视频当中采用到的,pts是显示时间戳,dts是解码时间戳;以后搞到视频再详细说明啦;不需要,对于写音频数据,系统,或者更准确的说驱动会自动调整,写得快,他会阻塞你的写,写的慢?你的机器该换了,嘿嘿,玩笑一个。。。增加缓存可以解决慢的问题;     <br /> Q:如何调试音频播放器?     <br /> 这里需要注意两点,一点是你要保证解码后的数据确实是PCM数据;第二点是你要确定数据准确无误,全部写入音频文件,否则会出现各种各样的暴音啊之类的事情,不可预测;     <br /> 有关这两点你可以分别调试;第一点,可以将解码后的数据写入一个文件当中,然后利用一些音频分析软件(能够分析PCM数据),播放即可,看你解码的数据是否正确,完整,如果没有问题,那这一步就完成了,我在这里没有卡壳,直接过;下一步,我是扔进去很多时间,由于我的指针使用不当,导致总是漏写数据,我在下面也会把我的错误代码贴出来,以做对比,大家也都可以来看看,这一点我想经常和指针打交道的就肯定没问题了的;     <br />    <br /> 这里向大家推荐windows下的cooledit软件,不用找注册码,反正能试用,没问题,功能绝对够用,而且分析声音频播非常形象,郑重推荐;虽然windows和linux切换麻烦了点,嘿嘿:)不过如果你有两台电脑,另说啦。。。     <br /> 下面将这个音频播放器的源代码贴出来,以便大家互相学习;     <br /> 我的编译环境是     <br /> os:Neoshine linux (2.6.14-1.1644_dt_5);     <br /> 硬件:普通pc机;     <br />    <pre class="brush:cpp; toolbar: true; auto-links: false;">/***************************************************************************  *            main.cc  *  *  Thu Nov  9 20:47:33 2006  *  Copyright  2006  *  Email lsosa.BIT  *  Author lsosa.BIT  ****************************************************************************/   #include <avcodec.h>  #include <avformat.h>  #include <avutil.h>  #include <assert.h>  #include <stdio.h>  #include <stdlib.h>  #include <X11/Xlib.h>  #include <sys/soundcard.h>  #include <sys/stat.h>  #include <fcntl.h>  #include <sys/ioctl.h>  #include <unistd.h>  #include <errno.h>  #include <string.h>  #include <sched.h>   #define ALL_DEBUG   #ifdef ALL_DEBUG      #define AV_DEBUG      #define AUDIO_DEBUG  #endif   //------------------------------------------------------------------------------  // manipulations for file  int open_file (char *file_name, int mode)  {      // open file file_name and return the file descriptor;      int fd;       if ((fd = open (file_name, mode)) < 0)      {          fprintf (stderr, " Can't open %s!\n", file_name);          exit (-1);      }      return fd;  }   int set_audio (int fd, AVCodecContext * pCodecCtx)  {      // set the properties of audio device with pCodecCtx;       int i, err;      /* 设置适当的参数,使得声音设备工作正常 */      /* 详细情况请参考Linux关于声卡编程的文档 */          i = 0;      ioctl (fd, SNDCTL_DSP_RESET, &i);      i = 0;      ioctl (fd, SNDCTL_DSP_SYNC, &i);      i = 1;      ioctl (fd, SNDCTL_DSP_NONBLOCK, &i);          // set sample rate;      #ifdef AUDIO_DEBUG      printf ("pCodecCtx->sample_rate:%d\n", pCodecCtx->sample_rate);      #endif      i = pCodecCtx->sample_rate;      if (ioctl (fd, SNDCTL_DSP_SPEED, &i) == -1)      {          fprintf (stderr, "Set speed to %d failed:%s\n", i,               strerror (errno));          return (-1);      }      if (i != pCodecCtx->sample_rate)      {          fprintf (stderr, "do not support speed %d,supported is %d\n",               pCodecCtx->sample_rate, i);          return (-1);      }          // set channels;      i = pCodecCtx->channels;      #ifdef AUDIO_DEBUG      printf ("pCodecCtx->channels:%d\n", pCodecCtx->channels);      #endif      if ((ioctl (fd, SNDCTL_DSP_CHANNELS, &i)) == -1)      {          fprintf (stderr, "Set Audio Channels %d failed:%s\n", i,               strerror (errno));          return (-1);      }      if (i != pCodecCtx->channels)      {          fprintf (stderr, "do not support channel %d,supported %d\n",              pCodecCtx->channels, i);          return (-1);      }      // set bit format;      i = AFMT_S16_LE;      if (ioctl (fd, SNDCTL_DSP_SETFMT, &i) == -1)      {          fprintf (stderr, "Set fmt to bit %d failed:%s\n", i,               strerror (errno));          return (-1);      }      if (i != AFMT_S16_LE)      {          fprintf (stderr, "do not support bit %d, supported %d\n",               AFMT_S16_LE, i);          return (-1);      }          // set application buffer size;      // i = (0x00032 << 16) + 0x000c;        // 32 4kb buffer;      // ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &i);      i = 1;      ioctl (fd, SNDCTL_DSP_PROFILE, &i);          return 0;  }   void close_file (int fd)  {      // close the file pointed by file descriptor fd;      close (fd);  }   //------------------------------------------------------------------------------  // handle audio;   void display_AVCodecContext(AVCodecContext *pCodecCtx){      //      #define STDOUT stderr      fprintf(STDOUT, "pCodecCtx->bit_rate:%d\n", pCodecCtx->bit_rate);      fprintf(STDOUT, "pCodecCtx->sample_rate:%d\n", pCodecCtx->sample_rate);      fprintf(STDOUT, "pCodecCtx->channels:%d\n", pCodecCtx->channels);      fprintf(STDOUT, "pCodecCtx->frame_size:%d\n", pCodecCtx->frame_size);      fprintf(STDOUT, "pCodecCtx->frame_number:%d\n", pCodecCtx->frame_number);      fprintf(STDOUT, "pCodecCtx->delay:%d\n", pCodecCtx->delay);      fprintf(STDOUT, "pCodecCtx->frame_bits:%d\n", pCodecCtx->frame_bits);  }   // error if return -1;  // success if return 0;  // 这里要用到指向指针的指针,否则传不到值;  int av_init (char *file_name, AVFormatContext ** pFormatCtx,       AVCodecContext ** pCodecCtx, int *p_audioStream)  {      // init the codec and format of input file file_name;      int audioStream, i;      AVCodec *pCodec;      // catch error      assert(file_name != NULL);      assert(*pFormatCtx != NULL);      assert(*pCodecCtx != NULL);          // Register all formats and codecs      av_register_all ();          // open file      if (av_open_input_file (pFormatCtx, file_name, NULL, 0, NULL) != 0){          // Couldn't open file          fprintf (stderr, " Can't open %s!\n", file_name);          return -1;        }       // Retrieve stream information      if (av_find_stream_info (*pFormatCtx) < 0){          // Couldn't find stream information          return -1;        }          #ifdef AV_DEBUG      // Dump information about file onto standard error      dump_format (*pFormatCtx, 0, file_name, false);      #endif          // Find the first audio and video stream respectively      audioStream = -1;      for (i = 0; i < (*pFormatCtx)->nb_streams; i++){          if ((*pFormatCtx)->streams->codec->codec_type ==              CODEC_TYPE_AUDIO)          {              audioStream = i;          }      }          #ifdef AV_DEBUG      // dump_stream_info(pFormatCtx);      #endif          // exclude error      if (audioStream == -1){          // Didn't find a audio or video stream          return -1;        }       // Get a pointer to the codec context for the audio stream      *pCodecCtx = (*pFormatCtx)->streams[audioStream]->codec;       // Find the decoder for the audio stream      pCodec = avcodec_find_decoder ((*pCodecCtx)->codec_id);      if (pCodec == NULL)          return -1;    // Codec not found       // Open codec      if (avcodec_open ((*pCodecCtx), pCodec) < 0){          return -1;    // Could not open codec      }          #ifdef AUDIO_DEBUG      // printf ("pCodecCtx->sample_rate:%d, audioStream:%d\n", (*pCodecCtx)->sample_rate, audioStream);      // display_AVCodecContext(*pCodecCtx);      #endif          *p_audioStream = audioStream;          return 0;  }   void av_play (AVFormatContext * pFormatCtx,       AVCodecContext * pCodecCtx, int audioStream)  {      // which was read from one frame;      AVPacket packet;      uint32_t len;      uint8_t decompressed_audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];      int decompressed_audio_buf_size;      uint8_t * p_decompressed_audio_buf;      int fd = -1;    // audio file or test file?      char filename[64] = "/dev/dsp";      int mode = O_WRONLY;      //          // open audio file or written file      // printf("fd:%d", fd);      fd = open_file(filename, mode);      // printf("fd:%d", fd);      //      set_audio(fd, pCodecCtx);          //      printf("(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2=%d\n", (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2);     printf("AVCODEC_MAX_AUDIO_FRAME_SIZE=%d\n", AVCODEC_MAX_AUDIO_FRAME_SIZE);          // for a test      // char test_file[256] = "my_pcm.pcm";      // fd = open_file(test_file, mode);          #ifdef AV_DEBUG      static int size = 0;      #endif      //          // set the sched priority      // 这是为了提高音频优先级;不晓得起作用没;      int policy = SCHED_FIFO;      sched_setscheduler(0, policy, NULL);          int write_buf_size = 4196;      int written_size;      while (av_read_frame (pFormatCtx, &packet) >= 0)      {          // Is this a packet from the audio stream?          // 判断是否音频帧;          if (packet.stream_index == audioStream)          {              // Decode audio frame              // 解码音频数据为pcm数据;              len = avcodec_decode_audio (pCodecCtx,                              (int16_t *)decompressed_audio_buf,                              &decompressed_audio_buf_size,        // it is the decompressed frame in BYTES 解码后的数据大小,字节为单位;                              packet.data,                              packet.size );              // printf("len:%d, packet.size:%d\n", len, packet.size);              if ( len < 0 ){                  // if error len = -1                  printf("+----- error in decoding audio frame\n");                  // exit(0);              }              // test lsosa                                      // printf("size = %d\n", size);              //******************************************************************              // 重点是这一部分,使用oss播放的代码,之前的数据写是否完整的问题就是出在这里,或者是前面的set_audio函数设置不正确;              // audio_buf_info info;              p_decompressed_audio_buf = decompressed_audio_buf;              while ( decompressed_audio_buf_size > 0 ){                  // 解码后数据不为零,则播放之,为零,则;                  written_size = write(fd, p_decompressed_audio_buf, decompressed_audio_buf_size);                  if ( written_size == -1 ){                      // printf("error:decompressed_audio_buf_size:%d, decompressed_audio_buf_size:%d, %s\n", \                                  decompressed_audio_buf_size, decompressed_audio_buf_size,strerror(errno));                      // usleep(100);                      continue;                  }                  // printf("decompressed_audio_buf_size:%d, written_size:%d\n", \                              decompressed_audio_buf_size, written_size);                  decompressed_audio_buf_size -= written_size;                  p_decompressed_audio_buf += written_size;                              }// end while              //******************************************************************          }          else          {              printf("+----- this is not audio frame\n");          }// end if          // Free the packet that was allocated by av_read_frame          av_free_packet (&packet);      }// end while of reading one frame;              close_file(fd);  }   void av_close (AVFormatContext * pFormatCtx, AVCodecContext * pCodecCtx)  {      // close the file and codec       // Close the codec      avcodec_close (pCodecCtx);       // Close the video file      av_close_input_file (pFormatCtx);  }   //------------------------------------------------------------------------------   int main (int argc, char **argv){      //      AVFormatContext *pFormatCtx;      int audioStream = -1;      AVCodecContext *pCodecCtx;          // exclude the error about args;      if ( argc != 2 ){          printf("please give a file name\n");          exit(0);      }          // 注意:这里要用到指向指针的指针,是因为这个初始化函数需要对指针的地址进行改动,      // 所以,只有这么做,才能达到目的;      if ( av_init(argv[1], &pFormatCtx, &pCodecCtx, &audioStream) < 0 ){          //          fprintf(stderr, "error when av_init\n");      }          // play the audio file      av_play(pFormatCtx, pCodecCtx, audioStream);          // close all the opend files      av_close(pFormatCtx, pCodecCtx);      } </pre>    <br />    <br />    <span style="color:#000000;"><span style="font-family:Helvetica,Tahoma,Arial,sans-serif;"><a href="/misc/goto?guid=4959517422745462687" target="_blank">http://weiyuhu.iteye.com/blog/576610</a></span></span>    <p></p>