整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          HTML網頁實現FLV視頻播放

          HTML網頁實現FLV視頻播放

          、HTML代碼如下:

          <div id="player"></div>
          

          二、JavaScript代碼如下:

          <script src="http://cdn.jsdelivr.net/npm/xgplayer/browser/index.js" charset="utf-8"></script>
          <script src="http://cdn.jsdelivr.net/npm/xgplayer-flv.js/browser/index.js" charset="utf-8"></script>
          <script type="text/javascript">
           new window.FlvJsPlayer({
           id: 'player',
           isLive: false,
           playsinline: true,
           url: '/test/test.flv',
           autoplay: true,
           height: window.innerHeight,
           width: window.innerWidth
           });
          </script>
          
          

          .環境準備

          音視頻數據用代碼生成,不是從文件讀取,然后再合成為flv。

          合成的flv。

          播放如下:

          2.FFMPEG合成音視頻框架


          (1)avformat_alloc_output_context2:分配AVFormatContext,并根據filename綁定合適的AVOutputFormat。

          (2)通過fmt->video_codec=AV_CODEC_ID_H264 和fmt->audio_codec=AV_CODEC_ID_AAC。

          (3)通過add_stream,添加流,設置編碼器參數。通過自定義的函數open_video和open_audio,關聯編碼器,分配幀buffer,初始化scale,初始化PCM的參數,初始化重采樣器。

          (4)通過avio_open,打開對應的輸出文件。

          (5)avformat_write_header : 寫?件頭

          (6)寫視頻幀和音頻幀,涉及到編碼音視頻,這里面也會涉及到time_base轉換。

          (7)av_write_frame/av_interleaved_write_frame: 寫packet

          (8)av_write_trailer : 寫?件尾,如果是實時流,可以不用寫文件尾。


          看看這些比較重要的函數和結構:

          avformat_write_header:

          注意:這里會改變音視頻各自的time_base,如,開始audio的 AVstream->base_time=1/44100, video 為AVstream->base_time=1/25。經過avformat_write_header后,audio和video的time_base就變為1/1000。這種改變,是與封裝格式有關系。比如ts和flv又是不一樣。

          上面圖中的紅框部分,就會調用到復用器的write_header,如:

          avformat_alloc_output_context2:

          函數在在libavformat.h??的定義。

          int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59

          AVOutputFormat *oformat,const char *format_name, const char *filename);

          ctx:需要創建的context,返回NULL表示失敗。

          oformat:指定對應的AVOutputFormat,如果不指定,可以通過后?format_name、filename兩個參數進?指定,讓ffmpeg??推斷。

          format_name: 指定?視頻的格式,?如“flv”,“mpeg”等,如果設置為NULL,則由filename進?指定,讓ffmpeg??推斷。

          filename: 指定?視頻?件的路徑,如果oformat、format_name為NULL,則ffmpeg內部根據filename后綴名選擇合適的復?器,?如xxx.flv則使?flv復?器。

          int avformat_alloc_output_context2(AVFormatContext **avctx, ff_const 59 AVOutputFormat *oformat, 2 const char *format, const char *f ilename) 3 { 4 AVFormatContext *s=avformat_alloc_context(); 5 int ret=0; 67 *avctx=NULL; 8 if (!s) 9 goto nomem; 1011 if (!oformat) { // oformat為NULL 12 if (format) { 13 oformat=av_guess_format(format, NULL, NULL); //根據提供 的格式進?查找 format 14 if (!oformat) { 15 av_log(s, AV_LOG_ERROR, "Requested output format '% s' is not a suitable output format\n", format); 16 ret=AVERROR(EINVAL); 17 goto error; 18 } 19 } else { // oformat和format都為NULL 20 oformat=av_guess_format(NULL, filename, NULL); // 根據 ?件名后綴進?查找 21 if (!oformat) { 22 ret=AVERROR(EINVAL); 23 av_log(s, AV_LOG_ERROR, "Unable to find a suitable o utput format for '%s'\n", 24 filename); 25 goto error; 26 } 27 } 28 } 2930 s->oformat=oformat; 31 if (s->oformat->priv_data_size > 0) { 32 s->priv_data=av_mallocz(s->oformat->priv_data_size); 33 if (!s->priv_data) 7
          34 goto nomem; 35 if (s->oformat->priv_class) { 36 *(const AVClass**)s->priv_data=s->oformat->priv_class; 37 av_opt_set_defaults(s->priv_data); 38 } 39 } else 40 s->priv_data=NULL; 4142 if (filename) { 43 #if FF_API_FORMAT_FILENAME 44 FF_DISABLE_DEPRECATION_WARNINGS 45 av_strlcpy(s->filename, filename, sizeof(s->filename)); 46 FF_ENABLE_DEPRECATION_WARNINGS 47 #endif 48 if (!(s->url=av_strdup(filename))) 49 goto nomem; 5051 } 52 *avctx=s; 53 return 0; 54 nomem: 55 av_log(s, AV_LOG_ERROR, "Out of memory\n"); 56 ret=AVERROR(ENOMEM); 57 error: 58 avformat_free_context(s); 59 return ret; 60 }

          可以看出,??最主要的就兩個函數avformat_alloc_context和av_guess_format,?個是申請內存分配上下?,?個是通過后?兩個參數獲取AVOutputFormat。av_guess_format這個函數會通過filename和short_name來和所有的編碼器進??對,找出最接近的編碼器然后返回。

          ff_const59 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
                                          const char *mime_type)
          {
              const AVOutputFormat *fmt=NULL;
              AVOutputFormat *fmt_found=NULL;
              void *i=0;
              int score_max, score;
          
              /* specific test for image sequences */
          #if CONFIG_IMAGE2_MUXER
              if (!short_name && filename &&
                  av_filename_number_test(filename) &&
                  ff_guess_image2_codec(filename) !=AV_CODEC_ID_NONE) {
                  return av_guess_format("image2", NULL, NULL);
              }
          #endif
              /* Find the proper file type. */
              score_max=0;
              while ((fmt=av_muxer_iterate(&i))) {
                  score=0;
                  if (fmt->name && short_name && av_match_name(short_name, fmt->name))
                     // fmt->name?如ff_flv_muxer的為"flv"
                    // 匹配了name 最?規格
                      score +=100;
                  if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
                      // ff_flv_muxer的為 "video/x-flv"
                    // 匹配mime_type
                    score +=10;
                  if (filename && fmt->extensions &&
                      av_match_ext(filename, fmt->extensions)) {
                      //ff_flv_muxe r的為 "flv"
                    // 匹配
                    score +=5;
                  }
                  if (score > score_max) {
                    // 更新最匹配的分值
                      score_max=score;
                      fmt_found=(AVOutputFormat*)fmt;
                  }
              }
              return fmt_found;
          }


          AVOutputFormat

          AVOutpufFormat表示輸出?件容器格式,AVOutputFormat 結構主要包含的信息有:封裝名稱描述,編碼格式信息(video/audio 默認編碼格式,?持的編碼格式列表),?些對封裝的操作函數(write_header,write_packet,write_tailer等)。

          ffmpeg?持各種各樣的輸出?件格式,MP4,FLV,3GP等等。? AVOutputFormat 結構體則保存了這些格式的信息和?些常規設置。每?種封裝對應?個 AVOutputFormat 結構,ffmpeg將AVOutputFormat 按照鏈表存儲:

          2.結構體定義

          typedef struct AVOutputFormat{
              const char *name;
              /**
               * Descriptive name for the format, meant to be more human-readable
               * than name. You should use the NULL_IF_CONFIG_SMALL() macro
               * to define it.
               */
              const char *long_name;
              const char *mime_type;
              const char *extensions; /**< comma-separated filename extensions */
              /* output support */
              enum AVCodecID audio_codec;    /**< default audio codec */
              enum AVCodecID video_codec;    /**< default video codec */
              enum AVCodecID subtitle_codec; /**< default subtitle codec */
              /**
               * can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER,
               * AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS,
               * AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH,
               * AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE
               */
              int flags;
          
              /**
               * List of supported codec_id-codec_tag pairs, ordered by "better
               * choice first". The arrays are all terminated by AV_CODEC_ID_NONE.
               */
              const struct AVCodecTag * const *codec_tag;
          
          
              const AVClass *priv_class; ///< AVClass for the private context
          
              /*****************************************************************
               * No fields below this line are part of the public API. They
               * may not be used outside of libavformat and can be changed and
               * removed at will.
               * New public fields should be added right above.
               *****************************************************************
               */
              /**
               * The ff_const59 define is not part of the public API and will
               * be removed without further warning.
               */
          #if FF_API_AVIOFORMAT
          #define ff_const59
          #else
          #define ff_const59 const
          #endif
              ff_const59 struct AVOutputFormat *next;
              /**
               * size of private data so that it can be allocated in the wrapper
               */
              int priv_data_size;
          
              int (*write_header)(struct AVFormatContext *);
              /**
               * Write a packet. If AVFMT_ALLOW_FLUSH is set in flags,
               * pkt can be NULL in order to flush data buffered in the muxer.
               * When flushing, return 0 if there still is more data to flush,
               * or 1 if everything was flushed and there is no more buffered
               * data.
               */
              int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
              int (*write_trailer)(struct AVFormatContext *);
              /**
               * Currently only used to set pixel format if not YUV420P.
               */
              int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
                                       AVPacket *in, int flush);
              /**
               * Test if the given codec can be stored in this container.
               *
               * @return 1 if the codec is supported, 0 if it is not.
               *         A negative number if unknown.
               *         MKTAG('A', 'P', 'I', 'C') if the codec is only supported as AV_DISPOSITION_ATTACHED_PIC
               */
              int (*query_codec)(enum AVCodecID id, int std_compliance);
          
              void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
                                           int64_t *dts, int64_t *wall);
              /**
               * Allows sending messages from application to device.
               */
              int (*control_message)(struct AVFormatContext *s, int type,
                                     void *data, size_t data_size);
          
              /**
               * Write an uncoded AVFrame.
               *
               * See av_write_uncoded_frame() for details.
               *
               * The library will free *frame afterwards, but the muxer can prevent it
               * by setting the pointer to NULL.
               */
              int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
                                         AVFrame **frame, unsigned flags);
              /**
               * Returns device list with it properties.
               * @see avdevice_list_devices() for more details.
               */
              int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
              /**
               * Initialize device capabilities submodule.
               * @see avdevice_capabilities_create() for more details.
               */
              int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
              /**
               * Free device capabilities submodule.
               * @see avdevice_capabilities_free() for more details.
               */
              int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
              enum AVCodecID data_codec; /**< default data codec */
              /**
               * Initialize format. May allocate data here, and set any AVFormatContext or
               * AVStream parameters that need to be set before packets are sent.
               * This method must not write output.
               *
               * Return 0 if streams were fully configured, 1 if not, negative AVERROR on failure
               *
               * Any allocations made here must be freed in deinit().
               */
              int (*init)(struct AVFormatContext *);
              /**
               * Deinitialize format. If present, this is called whenever the muxer is being
               * destroyed, regardless of whether or not the header has been written.
               *
               * If a trailer is being written, this is called after write_trailer().
               *
               * This is called if init() fails as well.
               */
              void (*deinit)(struct AVFormatContext *);
              /**
               * Set up any necessary bitstream filtering and extract any extra data needed
               * for the global header.
               * Return 0 if more packets from this stream must be checked; 1 if not.
               */
              int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
          }AVOutputFormat

          3.常?變量及其作?

          const char *name;// 復?器名稱

          const char *long_name;//格式的描述性名稱,易于閱讀。

          enum AVCodecID audio_codec; //默認的?頻編解碼器

          enum AVCodecID video_codec; //默認的視頻編解碼器

          enum AVCodecID subtitle_codec; //默認的字幕編解碼器

          注意:?部分復?器都有默認的編碼器,所以?家如果要調整編碼器類型則需要???動指定。

          ?如AVOutputFormat的ff_flv_muxer,flv默認就指定了MP3

          mpegts默認指定了,AV_CODEC_ID_MP2和AV_CODEC_ID_MPEG2VIDEO

          int (*write_header)(struct AVFormatContext *);//寫頭

          int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//寫?個數據包。 如果在標志中設

          置AVFMT_ALLOW_FLUSH,則pkt可以為NULL。

          int (*write_trailer)(struct AVFormatContext *);//寫尾部

          //交叉寫包

          int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush);

          int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允許從應?程序向設備發送消息。

          int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, AVFrame **frame,unsigned flags);//寫?個未編碼的AVFrame。

          //初始化格式。 可以在此處分配數據,并設置在發送數據包之前需要設置的任何AVFormatContext或AVStream參數。

          int (*init)(struct AVFormatContext *);

          void (*deinit)(struct AVFormatContext *);//取消初始化格式。

          int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//設置任何必要的?特流過濾,并提取全局頭部所需的任何額外數據。這個很關鍵。

          avformat_new_stream

          在 AVFormatContext 中創建 Stream 通道。

          AVStream 即是流通道。例如我們將 H264 和 AAC 碼流存儲為MP4?件的時候,就需要在 MP4?件中增加兩個流通道,?個存儲Video:H264,?個存儲Audio:AAC。(假設H264和AAC只包含單個流通道)。

          AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

          AVFormatContext :

          unsigned int nb_streams; 記錄stream通道數?。

          AVStream **streams; 存儲stream通道。

          AVStream :

          int index; 在AVFormatContext 中所處的通道索引

          avformat_new_stream之后便在 AVFormatContext ?增加了 AVStream 通道(相關的index已經被設置了)。之后,我們就可以??設置 AVStream 的?些參數信息。例如 : codec_id , format ,bit_rate,width , height。相當于這些參數就有了“依靠”。

          av_interleaved_write_frame

          函數原型:int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)

          功能:將數據包寫?輸出媒體?件,并確保正確的交織(保持packet dts的增?性)。該函數會在內部根據需要緩存packet,以確保輸出?件中的packet按dts遞增的順序正確交織。如果??進?交織則應調?av_write_frame()(沒有緩存)。如果沒有B幀的情況,正確設置pts后,這兩個接口表現出來的差不多。

          參數:

          返回值:成功時為0,錯誤時為負AVERROR。即使此函數調?失敗,Libavformat仍將始終釋放該packet。

          av_compare_ts

          int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);

          返回值:

          -1 ts_a 在ts_b之前。

          1 ts_a 在ts_b之后。

          0 ts_a 在ts_b同?位置。


          MediaInfo分析?件寫?

          這?只是分析avformat_write_header和av_write_trailer的作?。



          avformat_write_header+ av_write_trailer。對于FLV??沒有任何變化。

          mp4



          時間戳詳解

          參考原文地址:https://www.cnblogs.com/leisure_chn/p/10584910.html

          I幀/P幀/B幀

          I幀:I幀(Intra-coded picture, 幀內編碼幀,常稱為關鍵幀)包含?幅完整的圖像信息,屬于幀內編碼圖像,不含運動?量,在解碼時不需要參考其他幀圖像。因此在I幀圖像處可以切換頻道,?不會導致圖像丟失或?法解碼。I幀圖像?于阻?誤差的累積和擴散。在閉合式GOP中,每個GOP的第?個幀?定是I幀,且當前GOP的數據不會參考前后GOP的數據。

          P幀:P幀(Predictive-coded picture, 預測編碼圖像幀)是幀間編碼幀,利?之前的I幀或P幀進?預測編碼。

          B幀:B幀(Bi-directionally predicted picture, 雙向預測編碼圖像幀)是幀間編碼幀,利?之前和(或)之后的I幀或P幀進?雙向預測編碼。B幀不可以作為參考幀。B幀具有更?的壓縮率,但需要更多的緩沖時間以及更?的CPU占?率,因此B幀適合本地存儲以及視頻點播,?不適?對實時性要求較?的直播系統。

          DTS和PTS

          DTS(Decoding Time Stamp, 解碼時間戳),表示壓縮幀的解碼時間。

          PTS(Presentation Time Stamp, 顯示時間戳),表示將壓縮幀解碼后得到的原始幀的顯示時間。

          ?頻中DTS和PTS是相同的。視頻中由于B幀需要雙向預測B幀依賴于其前和其后的幀,因此含B幀的視頻解碼順序與顯示順序不同即DTS與PTS不同。當然,不含B幀的視頻,其DTS和PTS是相同的。下圖以?個開放式GOP示意圖為例,說明視頻流的解碼順序和顯示順序。


          采集順序:指圖像傳感器采集原始信號得到圖像幀的順序。

          編碼順序:指編碼器編碼后圖像幀的順序。存儲到磁盤的本地視頻?件中圖像幀的順序與編碼順序相同。

          傳輸順序:指編碼后的流在?絡中傳輸過程中圖像幀的順序。

          解碼順序:指解碼器解碼圖像幀的順序。

          顯示順序:序指圖像幀在顯示器上顯示的順序。

          注意:從圖中可以看出,采集順序與顯示順序相同編碼順序,傳輸順序和解碼順序相同

          以圖中“B[1]”幀為例進?說明,“B[1]”幀解碼時需要參考“I[0]”幀和“P[3]”幀,因此“P[3]”幀必須?“B[1]”幀先解碼。這就導致了解碼順序和顯示順序的不?致后顯示的幀需要先解碼


          時間與時間戳

          在FFmpeg中,時間基(time_base)是時間戳(timestamp)的單位,時間戳值乘以時間基,可以得到實際的時刻值(以秒等為單位)。例如,如果?個視頻幀的dts是40,pts是160,time_base是1/1000秒,那么可以計算出此視頻幀的解碼時刻是40毫秒(40/1000),顯示時刻是160毫秒(160/1000)。FFmpeg中時間戳(pts/dts)的類型是int64_t類型,把?個time_base看作?個時鐘脈沖,則可把dts/pts看作時鐘脈沖的計數


          三種時間基tbr、tbn和tbc

          不同封裝格式有不同的時間基。在FFmpeg處理?視頻過程中的不同階段,也會采?不同的時間基

          FFmepg中有三種時間基,命令?中tbr、tbn和tbc的打印值就是這三種時間基的倒數。如下:

          tbn:對應容器中的時間基。值是AVStream.time_base的倒數

          tbc:對應編解碼器中的時間基。值是AVCodecContext.time_base的倒數

          tbr:從視頻流中猜算得到,可能是幀率或場率(幀率的2倍)

          測試?件下載(右鍵另存為):tnmil3.flv。

          使?ffprobe探測媒體?件格式,如下:


          關于tbr、tbn和tbc的說明,原?如下,來?FFmpeg郵件列表:

          There are three different time bases for time stamps in FFmpeg. 
          The values printed are actually reciprocals of these, i.e. 1/tbr, 1/tbn and 1/tbc
          tbn is the time base in AVStream that has come from the container,
            I think. It is used for all AVStream time stamps。
            
            tbc is the time base in AVCodecContext for the codec used for a particular stream.
            It is used for all AVCodecContext and related time stamps.


          內部時間基AV_TIME_BASE

          除以上三種時間基外,FFmpeg還有?個內部時間基AV_TIME_BASE(以及分數形式的AV_TIME_BASE_Q)。

          AV_TIME_BASE及AV_TIME_BASE_Q?于FFmpeg內部函數處理,使?此時間基計算得到時間值表示的是微秒

          時間值形式轉換

          av_q2d()將時間從AVRational形式轉換為double形式。AVRational是分數類型,double是雙精度浮點數類型,轉換的結果單位是秒。轉換前后的值基于同?時間基,僅僅是數值的表現形式不同?已。

          av_q2d()實現如下:


          av_q2d()使??法如下:

          注意:時刻表示的是一個瞬間值,時長表示能持續的時間。

          時間基轉換函數

          av_rescale_q()?于不同時間基的轉換,?于將時間值從?種時間基轉換為另?種時間基。

          a數值bq時間基轉成 cq的時間基,通過返回結果獲取以cq時間基表示的新數值。如下:

          int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)。這個函數的用法就更復雜,但是一般你可以選擇不用。

          它的作?是計算 "a * b / c" 的值并分五種?式來取整。按照如下方式:

          // Round toward zero(可以理解為向下取整). 趨近于0, round(2.5) 為 2, ?round(-2.5) 為 -2

          AV_ROUND_ZERO=0,

          // Round away from zero(可以理解為向上取整)。 趨遠于0 round(3.5)=4, round(-3.5)=-4

          AV_ROUND_INF=1,

          // Round toward -infinity.向負?窮??向 [-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-3, -2, 2, 5, 7, 2]

          AV_ROUND_DOWN=2,

          // Round toward +infinity. 向正?窮??向[-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-2, -1, 3, 6, 7, 3]

          AV_ROUND_UP=3,

          // 四舍五?,?于0.5取值趨向0,?于0.5取值趨遠于0

          AV_ROUND_NEAR_INF=5,


          av_packet_rescale_ts()?于將AVPacket中各種時間值從?種時間基轉換為另?種時間基


          轉封裝過程中的時間轉換

          容器中的時間基(AVStream.time_base,3.2節中的tbn)定義如下:


          注意:AVStream.time_baseAVPacket中pts和dts的時間單位,輸?流與輸出流中time_base按如下?式確定:

          對于輸?流:打開輸??件后,調?avformat_find_stream_info()獲取到每個流中的time_base。

          對于輸出流:打開輸出?件后,調?avformat_write_header()可根據輸出?件封裝格式確定每個流的time_base并寫?輸出?件中。

          不同封裝格式具有不同的時間基,在轉封裝(將?種封裝格式轉換為另?種封裝格式)過程中,時間基轉換相關代碼如下:

          一般在以下都是一段固定的代碼,在推流前,一般都是這么寫的:

          av_read_frame(ifmt_ctx, &pkt);
          
          pkt.pts=av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream- >time_base,  AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
          
          pkt.dts=av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream- >time_base,  AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
          
          pkt.duration=av_rescale_q(pkt.duration, in_stream->time_base, out_s tream->time_base);

          下?的代碼具有和上?代碼相同的效果:

           // 從輸??件中讀取packet
          av_read_frame(ifmt_ctx, &pkt);
          // 將packet中的各時間值從輸?流封裝格式時間基轉換到輸出流封裝格式時間基
          av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_bas e);

          這?流?的時間基 in_stream->time_base 和 out_stream->time_base ,是容器中的時間基,就是上面所說的tbn。

          例如,flv封裝格式的time_base為{1,1000},ts封裝格式的time_base為{1,90000}

          我們編寫程序將flv封裝格式轉換為ts封裝格式,抓取原?件(flv)的前四幀顯示時間戳

          再抓取轉換的?件(ts)的前四幀顯示時間戳:


          可以發現,對于同?個視頻幀,它們時間基(tbn)不同因此時間戳(pkt_pts)也不同,但是計算出來的時刻值(pkt_pts_time)是相同的。

          比如,第一幀這樣計算。

          看第?幀的時間戳,計算關系:80×{1,1000}==7200×{1,90000}==0.080000。

          一定要記住一個結論就是,時間戳可以不一樣,但是時刻值肯定是一樣


          轉碼過程中的時間基轉換

          編解碼器中的時間基(AVCodecContext.time_base,3.2節中的tbc)定義如下:


          上述注釋指出,AVCodecContext.time_base是幀率(視頻幀)的倒數,每幀時間戳遞增1,那么tbc就等于幀率。編碼過程中,應由?戶設置好此參數。解碼過程中,此參數已過時建議直接使?幀率倒數?作時間基。

          這?有?個問題:按照此處注釋說明,幀率為25的視頻流,tbc理應為25,但實際值卻為50,不知作何解釋?是否tbc已經過時,不具參考意義?

          所以這個結論就是:

          根據注釋中的建議,實際使?時,在視頻解碼過程中,我們不使?AVCodecContext.time_base,??幀率倒數作時間基,在視頻編碼過程中,我們將AVCodecContext.time_base設置為幀率的倒數


          視頻流

          視頻按幀播放,所以解碼后的原始視頻幀時間為 1/framerate

          視頻解碼過程中的時間基轉換處理,packet的pts到底什么,要看實際的情況,從av_read_frame讀取的packet,是以AVSteam->time_base,送給解碼器之前沒有必要轉成AVcodecContext->time_base。

          需要注意的是avcodec_receive_frame后以AVSteam->time_base為單位即可。

          也就是以下這段代碼,沒有必要進行時間基的轉換。

          AVFormatContext *ifmt_ctx;
          AVStream *in_stream;
          AVCodecContext *dec_ctx;
          AVPacket packet;
          AVFrame *frame;
          
          // 從輸??件中讀取編碼幀
          av_read_frame(ifmt_ctx, &packet);
          // 時間基轉換
          int raw_video_time_base=av_inv_q(dec_ctx->framerate);
          av_packet_rescale_ts(packet, in_stream->time_base, raw_video_time_ba se);
          // 解碼
          avcodec_send_packet(dec_ctx, packet);
          avcodec_receive_frame(dec_ctx, frame);

          視頻編碼過程中的時間基轉換處理。編碼的時候frame如果以AVstream為time_base送編碼器,則avcodec_receive_packet讀取的時候也是可以轉成AVSteam->time_base,也就是自動可以轉換。也就是具體情況,具體分析。

          AVFormatContext *ofmt_ctx;
          AVStream *out_stream; 
          AVCodecContext *dec_ctx; 
          AVCodecContext *enc_ctx; 
          AVPacket packet;
           AVFrame *frame; 
          // 編碼 
           avcodec_send_frame(enc_ctx, frame); 
          //這里內部就可以轉換AVSteam->time_base
           avcodec_receive_packet(enc_ctx, packet); 
          // 時間基轉換 
          packet.stream_index=out_stream_idx; 
          enc_ctx->time_base=av_inv_q(dec_ctx->framerate);
          
          av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_ base);
          // 將編碼幀寫?輸出媒體?件
           av_interleaved_write_frame(o_fmt_ctx, &packet);


          ?頻流

          對于?頻流也是類似的,本質來講就是具體情況具體分析,?如ffplay 解碼播放時就是AVSteam的time_base為基準的packet。然后出來的frame再?AVSteam的time_base對應的將pts轉成秒(使用內部時間基轉換)。

          但是要注意的是ffplay做了?個?較隱秘的設置:avctx->pkt_timebase=ic->streams[stream_index]->time_base; 即是對應的codeccontext??對pkt_timebase設置,和AVStream?樣的time_base

          ?頻按采樣點播放,所以解碼后的原始?頻幀時間基為 1/sample_rate

          ?頻解碼過程中的時間基轉換處理:

          AVFormatContext *ifmt_ctx; 
          AVStream *in_stream; 
           AVCodecContext *dec_ctx; 
           AVPacket packet; 
           AVFrame *frame; 
           // 從輸??件中讀取編碼幀
           av_read_frame(ifmt_ctx, &packet); 
           // 時間基轉換 
           int raw_audio_time_base=av_inv_q(dec_ctx->sample_rate); 
          10 av_packet_rescale_ts(packet, in_stream->time_base, raw_audio_time_ba se); 
          11 // 解碼 
          12 avcodec_send_packet(dec_ctx, packet) 
          13 avcodec_receive_frame(dec_ctx, frame);


          ?頻編碼過程中的時間基轉換處理:

          AVFormatContext *ofmt_ctx;
           AVStream *out_stream;
           AVCodecContext *dec_ctx; 
           AVCodecContext *enc_ctx; 
          AVPacket packet; 
           AVFrame *frame; 
           // 編碼  
          avcodec_send_frame(enc_ctx, frame);
          avcodec_receive_packet(enc_ctx, packet); 
           // 時間基轉換 
           packet.stream_index=out_stream_idx; 
           enc_ctx->time_base=av_inv_q(dec_ctx->sample_rate); 
           av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_ base); 
          // 將編碼幀寫?輸出媒體?件
           av_interleaved_write_frame(o_fmt_ctx, &packet);
          

          3.分析實戰代碼


          封裝音視頻編碼相關的數據結構。包括編碼器上下文,每一個stream,音頻的采樣數量,重采樣前后的frame等,詳細看如下圖:

          定義AVOutputFormat *fmt,輸出文件容器格式, 封裝了復用規則,比如,ff_flv_muxer。

          分配AVFormatContext并根據filename綁定合適的AVOutputFormat,如果如果不能根據文件后綴名找到合適的格式,那缺省使用flv格式。并獲取一些參數進行填充到輸出結構里。如下:

          使用指定的音視頻編碼格式增加音頻流和視頻流,如果不想要音頻或視頻,那這個fmt->video_codec可以指定為AV_CODEC_ID_NONE。

          這個add_stream具體是做什么工作呢?

          查找編碼器,然后新建碼流,并綁定到 AVFormatContext。

          如圖,這樣好理解點。



          現在指定編碼器上下文的索引,默認索引值為-1,每次調用avformat_new_stream的時候nb_streams+1。但id是從0開始, 比如第1個流:對應流id=nb_streams(1) -1=0,第2個流:對應流id=nb_streams(2) -1=1。

          緊接著就是創建編碼器上下文。

          初始化音視頻編碼器的一些參數。

          初始化音頻參數:

          初始化視頻參數:

          添加完流后,就要去open_video,看看具體做什么工作?

          首先就是關聯編碼器,分配幀buffer,這里使用的是32字節對齊,如果編碼器格式需要的數據不是 AV_PIX_FMT_YUV420P才需要 調用圖像scale,一般H264是支持,需要轉換。還需要把編碼器上下文的一些參數拷貝過來。


          添加完流后,也要去open_audio,看看具體做什么工作?

          也要關聯編碼器,初始化PCM參數。配送給編碼器的幀, 并申請對應的buffer。也需要拷貝參數,如果采樣格式不符合要求,也是要創建音頻重采樣。

          注意:這里會設置codec_ctx->time_base,這里就為1/44100。


          做了前面的一些關聯,參數設置后。接下來就要打開對應輸出文件。


          再開始就是寫入頭部,注意這里只是調用了一次。在寫入頭部是,對應steam的time_base被改寫,時間發生變化。


          重頭戲,這里就開始循環寫入音頻和視頻數據了。其中av_compare_ts,就是用來比較音頻pts和視頻pts大小,做一個同步處理,使得pts都有序,以音頻pts為基準。


          看看write_audio_frame,具體工作干了什么?

          獲取音頻數據,進行格式轉換,時間基數轉換,添加pts。

          這里通過av_compare_ts,控制了一個音頻編碼寫入時長。

          然后再編碼音頻,再寫pkt到文件里。

          看看write_video_frame,又主要干了什么?

          主要就是獲取視頻數據,編碼視頻,然后寫PKT到文件(同樣也只產生5s中的數據)。

          獲取視頻數據,進行像素格式轉換。

          在 write_frame這里,會進行一個時間基數再次轉換。

          pts_before * 1/44100=pts_after *1/1000。

          pts_after=pts_before * 1/44100 * 1000=-1024 * 1/44100 * 1000=-23。相當于要存儲一個在文件中,合適的時間。也不要認為這里的單位一定是ms。一般我在這里,會根據duration去計算pts。

          做了轉換后,新的pts,dts,duration,都會做轉換。

          再看看video 的pts變化。


          所以前面的工作都執行完后,需要調用av_write_trailer,MP4和FLV是有很大區別,如果MP4不調用這個函數,最后是無法播放。


          以上這篇文章分析到這里,歡迎關注,點贊,轉發,收藏。如果需要測試代碼,可以私信。

          lv.js 是 HTML5 Flash 視頻(FLV)播放器,純原生 JavaScript 開發,沒有用到 Flash。由 bilibili 網站開源。


          引入js

              <script src="flv.min.js"></script>

          視圖

          <div class="main">
                  <video id="videoElement" class="centeredVideo" controls autoplay width="1024" height="576">Your browser is too old which doesn't support HTML5 video.</video>
              </div>
              <br>
              <div class="controls">
                  <!--<button onclick="flv_load()">加載</button>-->
                  <button onclick="flv_start()">開始</button>
                  <button onclick="flv_pause()">暫停</button>
                  <button onclick="flv_destroy()">停止</button>
                  <input style="width:100px" type="text" name="seekpoint" />
                  <button onclick="flv_seekto()">跳轉</button>
              </div>


          js


          主站蜘蛛池模板: 国产精品香蕉一区二区三区| 秋霞午夜一区二区| 免费一区二区无码东京热| 亚洲欧洲精品一区二区三区| 中文字幕亚洲一区二区va在线| 日韩在线视频一区二区三区| 久久一区二区三区精华液使用方法 | 亚洲乱码一区av春药高潮 | 精品福利一区二区三| 伊人激情AV一区二区三区| 亚洲熟妇av一区二区三区漫画| 日韩精品成人一区二区三区| 人妻无码一区二区三区AV| 国产香蕉一区二区在线网站| 女同一区二区在线观看| 国产一区二区三区在线观看影院| 内射白浆一区二区在线观看| 久夜色精品国产一区二区三区| 亚洲国产综合精品一区在线播放| 国产亚洲情侣一区二区无| 真实国产乱子伦精品一区二区三区 | 久久国产免费一区| 无码人妻一区二区三区在线视频| 国产一区二区免费| 精品一区二区三区自拍图片区| 无码人妻aⅴ一区二区三区有奶水| 国产在线观看91精品一区| 波多野结衣免费一区视频| 亚洲一区精彩视频| 亚洲国产精品成人一区| www一区二区三区| 美女福利视频一区| 日韩一区二区三区射精| 免费无码一区二区三区蜜桃| 日韩精品一区二区三区中文| 国产一区二区三区免费看| 精品一区二区三区波多野结衣| 精品无码国产AV一区二区三区| 久久久久一区二区三区| 亚洲AV日韩综合一区尤物| 亚洲狠狠久久综合一区77777|