ffmpeg源碼分析1-avformat_open_input

本文參考雷神博文:https://blog.csdn.net/leixiaohua1020润努。

FFMPEG打開媒體的的過程開始于avformat_open_input宏多,因此該函數(shù)的重要性不可忽視
//參數(shù)ps統(tǒng)領(lǐng)整個上下文只洒,
//會返回一個AVFormatContext的實例.
//參數(shù)filename是媒體文件名或URL.
//參數(shù)fmt是要打開的媒體格式的操作結(jié)構(gòu)祝钢,因為是讀窖杀,所以是inputFormat.此處可以
//傳入一個使用者定義的inputFormat角骤,對應(yīng)命令行中的 -f xxx段,如果指定了它器赞,
//在打開文件中就不會探測文件的實際格式了垢袱,以它為準(zhǔn)了.
//參數(shù)options是對某種格式的一些操作,是為了在命令行中可以對不同的格式傳入
//特殊的操作參數(shù)而建的港柜, 為了了解流程,完全可以無視它.

ps:函數(shù)調(diào)用成功之后處理過的AVFormatContext結(jié)構(gòu)體。
file:打開的視音頻流的URL夏醉。
fmt:強(qiáng)制指定AVFormatContext中AVInputFormat的爽锥。這個參數(shù)一般情況下可以設(shè)置為NULL,這樣FFmpeg可以自動檢測AVInputFormat畔柔。
dictionay:附加的一些選項氯夷,一般情況下可以設(shè)置為NULL

在該函數(shù)中,F(xiàn)FMPEG完成了:
輸入輸出結(jié)構(gòu)體AVIOContext的初始化靶擦;
輸入數(shù)據(jù)的協(xié)議(例如RTMP腮考,或者file)的識別(通過一套評分機(jī)制):1判斷文件名的后綴 2讀取文件頭的數(shù)據(jù)進(jìn)行比對;
使用獲得最高分的文件協(xié)議對應(yīng)的URLProtocol玄捕,通過函數(shù)指針的方式踩蔚,與FFMPEG連接(非專業(yè)用詞);
剩下的就是調(diào)用該URLProtocol的函數(shù)進(jìn)行open,read等操作了

int avformat_open_input(AVFormatContext **ps, const char *filename,
                      AVInputFormat *fmt, AVDictionary **options)
{
  AVFormatContext *s = *ps;
  int i, ret = 0;
  AVDictionary *tmp = NULL;
  ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//預(yù)防之前沒有申請空間這里再申請一次枚粘,如果還是失敗就直接return
  if (!s && !(s = avformat_alloc_context()))
      return AVERROR(ENOMEM);
  if (!s->av_class) {
      av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
      return AVERROR(EINVAL);
  }
//如果指定了輸入的格式馅闽,這樣直接使用
  if (fmt)
      s->iformat = fmt;

  if (options)
      av_dict_copy(&tmp, *options, 0);

  if (s->pb) // must be before any goto fail
      s->flags |= AVFMT_FLAG_CUSTOM_IO;

  if ((ret = av_opt_set_dict(s, &tmp)) < 0)
      goto fail;

//復(fù)制文件名或者流的url給url指針,strdup申請了空間放置字符串并返回
  if (!(s->url = av_strdup(filename ? filename : ""))) {
      ret = AVERROR(ENOMEM);
      goto fail;
  }

#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
//標(biāo)準(zhǔn)庫函數(shù)strlcpy馍迄,是更加安全版本的strcpy函數(shù)福也,把filename復(fù)制到s->filename
  av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif

//該函數(shù)主要是初始化AVInputFormat結(jié)構(gòu)體,主要是調(diào)用av_probe_input_buffer2函數(shù)探測碼流格式    這個函數(shù)對里面的函數(shù)指針seek跟close等賦值
//執(zhí)行完此函數(shù)后攀圈,s->pb和s->iformat都已經(jīng)指向了有效實例.pb是用于讀寫數(shù)據(jù)的暴凑,它  
  //把媒體數(shù)據(jù)當(dāng)做流來讀寫,不管是什么媒體格式赘来,而iformat把pb讀出來的流按某種媒體格  
  //式進(jìn)行分析现喳,也就是說pb在底層,iformat在上層.
//絕大部分初始化工作都是在這里做的
  if ((ret = init_input(s, filename, &tmp)) < 0)
      goto fail;
/**
   * format probing score.
   * The maximal score is AVPROBE_SCORE_MAX, its set when the demuxer probes
   * the format.
   * - encoding: unused
   * - decoding: set by avformat, read by user
   */
  s->probe_score = ret;

  if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
      s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
      if (!s->protocol_whitelist) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
  }

  if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
      s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
      if (!s->protocol_blacklist) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
  }

  if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
      av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
      ret = AVERROR(EINVAL);
      goto fail;
  }
//直接將文件指針指向 s->skip_initial_bytes位置
  avio_skip(s->pb, s->skip_initial_bytes);
//很多靜態(tài)圖像文件格式撕捍,都被當(dāng)作一個格式處理拿穴,比如要打開.jpeg文件,需要的格式  
  //名為image2
  /* Check filename in case an image number is expected. */
//檢查文件名忧风,在期望的圖像編號的情況下檢查文件名默色,后續(xù)再了解此處細(xì)節(jié)
  if (s->iformat->flags & AVFMT_NEEDNUMBER) {
      if (!av_filename_number_test(filename)) {
          ret = AVERROR(EINVAL);
          goto fail;
      }
  }

  s->duration = s->start_time = AV_NOPTS_VALUE;

  /* Allocate private data. */
//分配私有數(shù)據(jù),此結(jié)構(gòu)的size在定義AVInputFormat時已指定了
  if (s->iformat->priv_data_size > 0) {
      if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
//這里的用處還沒細(xì)看狮腿,先不管
      if (s->iformat->priv_class) {
          *(const AVClass **) s->priv_data = s->iformat->priv_class;
          av_opt_set_defaults(s->priv_data);
          if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
              goto fail;
      }
  }

  /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
//從MP3文件讀取id3
  if (s->pb)
      ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

//讀一下媒體的頭部腿宰,根據(jù)流的數(shù)量分配流結(jié)構(gòu)并初始化,把文件指針指向數(shù)據(jù)區(qū)開始處等.
read_header函數(shù)指針在init_input函數(shù)里面賦值
//讀取多媒體數(shù)據(jù)文件頭缘厢,根據(jù)視音頻流創(chuàng)建相應(yīng)的AVStream吃度。
  if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
      if ((ret = s->iformat->read_header(s)) < 0)
          goto fail;

  if (!s->metadata) {
      s->metadata = s->internal->id3v2_meta;
      s->internal->id3v2_meta = NULL;
  } else if (s->internal->id3v2_meta) {
      int level = AV_LOG_WARNING;
      if (s->error_recognition & AV_EF_COMPLIANT)
          level = AV_LOG_ERROR;
      av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
      av_dict_free(&s->internal->id3v2_meta);
      if (s->error_recognition & AV_EF_EXPLODE)
          return AVERROR_INVALIDDATA;
  }

  if (id3v2_extra_meta) {
      if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
          !strcmp(s->iformat->name, "tta")) {
          if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
              goto fail;
          if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
              goto fail;
          if ((ret = ff_id3v2_parse_priv(s, &id3v2_extra_meta)) < 0)
              goto fail;
      } else
          av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
  }
  ff_id3v2_free_extra_meta(&id3v2_extra_meta);
//附屬照片?贴硫?
  if ((ret = avformat_queue_attached_pictures(s)) < 0)
      goto fail;

// s->internal->data_offset保存數(shù)據(jù)區(qū)開始的位置 
  if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
      s->internal->data_offset = avio_tell(s->pb);

  s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
//更新一下結(jié)構(gòu)體參數(shù)
  update_stream_avctx(s);

  for (i = 0; i < s->nb_streams; i++)
      s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;

  if (options) {
      av_dict_free(options);
      *options = tmp;
  }
  *ps = s;
  return 0;

fail:
  ff_id3v2_free_extra_meta(&id3v2_extra_meta);
  av_dict_free(&tmp);
  if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
      avio_closep(&s->pb);
  avformat_free_context(s);
  *ps = NULL;
  return ret;
}

下面是這個函數(shù)的調(diào)用結(jié)構(gòu)圖:


image.png


init_input()

此函數(shù)分析在ffmpeg源碼分析3-init_input



AVInputFormat-> read_header()

在調(diào)用完init_input()完成基本的初始化并且推測得到相應(yīng)的AVInputFormat之后椿每,avformat_open_input()會調(diào)用AVInputFormat的read_header()方法讀取媒體文件的文件頭并且完成相關(guān)的初始化工作伊者。read_header()是一個位于AVInputFormat結(jié)構(gòu)體中的一個函數(shù)指針,對于不同的封裝格式间护,會調(diào)用不同的read_header()的實現(xiàn)函數(shù)亦渗。舉個例子,當(dāng)輸入視頻的封裝格式為MOV的時候汁尺,會調(diào)用mov的AVInputFormat中的read_header()法精。mov的AVInputFormat定義位于libavformat\mov.c文件中,如下所示痴突。

AVInputFormat ff_mov_demuxer = {
    .name           = "mov,mp4,m4a,3gp,3g2,mj2",
    .long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
    .priv_class     = &mov_class,
    .priv_data_size = sizeof(MOVContext),
    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2",
    .read_probe     = mov_probe,
    .read_header    = mov_read_header,
    .read_packet    = mov_read_packet,
    .read_close     = mov_read_close,
    .read_seek      = mov_read_seek,
    .flags          = AVFMT_NO_BYTE_SEEK,
};

可以看出read_header()指向了mov_read_header()函數(shù)搂蜓。mov_read_header()的實現(xiàn)同樣位于libavformat\mov.c文件中,如下所示辽装。

static int mov_read_header(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    AVIOContext *pb = s->pb;
    int j, err;
    MOVAtom atom = { AV_RL32("root") };
    int i;

    if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {
        av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",
            mov->decryption_key_len, AES_CTR_KEY_SIZE);
        return AVERROR(EINVAL);
    }

    mov->fc = s;
    mov->trak_index = -1;
    /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
    if (pb->seekable & AVIO_SEEKABLE_NORMAL)
        atom.size = avio_size(pb);
    else
        atom.size = INT64_MAX;

    /* check MOV header */
    do {
        if (mov->moov_retry)
            avio_seek(pb, 0, SEEK_SET);
        if ((err = mov_read_default(mov, pb, atom)) < 0) {
            av_log(s, AV_LOG_ERROR, "error reading header\n");
            mov_read_close(s);
            return err;
        }
    } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
    if (!mov->found_moov) {
        av_log(s, AV_LOG_ERROR, "moov atom not found\n");
        mov_read_close(s);
        return AVERROR_INVALIDDATA;
    }
    av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb));

    if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
        if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters)
            mov_read_chapters(s);
        for (i = 0; i < s->nb_streams; i++)
            if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) {
                mov_read_timecode_track(s, s->streams[i]);
            } else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) {
                mov_read_rtmd_track(s, s->streams[i]);
            }
    }

    /* copy timecode metadata from tmcd tracks to the related video streams */
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        if (sc->timecode_track > 0) {
            AVDictionaryEntry *tcr;
            int tmcd_st_id = -1;

            for (j = 0; j < s->nb_streams; j++)
                if (s->streams[j]->id == sc->timecode_track)
                    tmcd_st_id = j;

            if (tmcd_st_id < 0 || tmcd_st_id == i)
                continue;
            tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0);
            if (tcr)
                av_dict_set(&st->metadata, "timecode", tcr->value, 0);
        }
    }
    export_orphan_timecode(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        fix_timescale(mov, sc);
        if(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && st->codecpar->codec_id == AV_CODEC_ID_AAC) {
            st->skip_samples = sc->start_pad;
        }
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0)
            av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                      sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
            if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {
                st->codecpar->width  = sc->width;
                st->codecpar->height = sc->height;
            }
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
                if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0)
                    return err;
            }
        }
        if (mov->handbrake_version &&
            mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2
            st->codecpar->codec_id == AV_CODEC_ID_MP3
        ) {
            av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");
            st->need_parsing = AVSTREAM_PARSE_FULL;
        }
    }

    if (mov->trex_data) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (st->duration > 0) {
                if (sc->data_size > INT64_MAX / sc->time_scale / 8) {
                    av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    mov_read_close(s);
                    return AVERROR_INVALIDDATA;
                }
                st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale / st->duration;
            }
        }
    }

    if (mov->use_mfra_for > 0) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (sc->duration_for_fps > 0) {
                if (sc->data_size > INT64_MAX / sc->time_scale / 8) {
                    av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    mov_read_close(s);
                    return AVERROR_INVALIDDATA;
                }
                st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale /
                    sc->duration_for_fps;
            }
        }
    }

    for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) {
        if (mov->bitrates[i]) {
            s->streams[i]->codecpar->bit_rate = mov->bitrates[i];
        }
    }

    ff_rfps_calculate(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        switch (st->codecpar->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            err = ff_replaygain_export(st, s->metadata);
            if (err < 0) {
                mov_read_close(s);
                return err;
            }
            break;
        case AVMEDIA_TYPE_VIDEO:
            if (sc->display_matrix) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix,
                                              sizeof(int32_t) * 9);
                if (err < 0)
                    return err;

                sc->display_matrix = NULL;
            }
            if (sc->stereo3d) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,
                                              (uint8_t *)sc->stereo3d,
                                              sizeof(*sc->stereo3d));
                if (err < 0)
                    return err;

                sc->stereo3d = NULL;
            }
            if (sc->spherical) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,
                                              (uint8_t *)sc->spherical,
                                              sc->spherical_size);
                if (err < 0)
                    return err;

                sc->spherical = NULL;
            }
            if (sc->mastering) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
                                              (uint8_t *)sc->mastering,
                                              sizeof(*sc->mastering));
                if (err < 0)
                    return err;

                sc->mastering = NULL;
            }
            if (sc->coll) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
                                              (uint8_t *)sc->coll,
                                              sc->coll_size);
                if (err < 0)
                    return err;

                sc->coll = NULL;
            }
            break;
        }
    }
    ff_configure_buffers_for_index(s, AV_TIME_BASE);

    for (i = 0; i < mov->frag_index.nb_items; i++)
        if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset)
            mov->frag_index.item[i].headers_read = 1;

    return 0;
}

可以看出帮碰,函數(shù)讀取了mov的文件頭并且判斷其中是否包含視頻流和音頻流,創(chuàng)建相應(yīng)的視頻流和音頻流如迟。
經(jīng)過上面的步驟AVInputFormat的read_header()完成了視音頻流對應(yīng)的AVStream的創(chuàng)建收毫。至此,avformat_open_input()中的主要代碼分析完畢殷勘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末此再,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子玲销,更是在濱河造成了極大的恐慌输拇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贤斜,死亡現(xiàn)場離奇詭異策吠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瘩绒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門猴抹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锁荔,你說我怎么就攤上這事蟀给。” “怎么了阳堕?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵跋理,是天一觀的道長。 經(jīng)常有香客問我恬总,道長前普,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任壹堰,我火速辦了婚禮拭卿,結(jié)果婚禮上骡湖,老公的妹妹穿的比我還像新娘。我一直安慰自己记劈,他們只是感情好勺鸦,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布并巍。 她就那樣靜靜地躺著目木,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懊渡。 梳的紋絲不亂的頭發(fā)上刽射,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音剃执,去河邊找鬼誓禁。 笑死,一個胖子當(dāng)著我的面吹牛肾档,可吹牛的內(nèi)容都是我干的摹恰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼怒见,長吁一口氣:“原來是場噩夢啊……” “哼俗慈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起遣耍,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤闺阱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舵变,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酣溃,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年纪隙,在試婚紗的時候發(fā)現(xiàn)自己被綠了赊豌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赖欣。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡土涝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岭粤,到底是詐尸還是另有隱情麸拄,我是刑警寧澤派昧,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站拢切,受9級特大地震影響蒂萎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淮椰,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一五慈、第九天 我趴在偏房一處隱蔽的房頂上張望纳寂。 院中可真熱鬧,春花似錦泻拦、人聲如沸毙芜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腋粥。三九已至,卻和暖如春架曹,著一層夾襖步出監(jiān)牢的瞬間隘冲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工绑雄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留展辞,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓万牺,卻偏偏與公主長得像罗珍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脚粟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容