音視頻開發(fā)之旅(34) - 基于FFmpeg實(shí)現(xiàn)簡單的視頻解碼器

目錄

  1. FFmpeg解碼過程流程圖和關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)
  2. mp4通過FFmpeg解碼YUV裸視頻數(shù)據(jù)
  3. 遇到的問題
  4. 資料
  5. 收獲

一忧饭、FFmpeg解碼過程流程圖和關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)

FFmpeg解碼涉及的知識(shí)點(diǎn)比較多岸售,很容易被函數(shù)和結(jié)構(gòu)體搞定不知所錯(cuò),我們先從整體上對(duì)解碼流程有個(gè)認(rèn)知矫夷,畫了張圖把解碼流程圖捉蚤,如下


1.1 解碼流程如下

  1. avformat_open_input 打開媒體文件
  2. avformat_find_stream_info 初始化AVFormatContext_
  3. 匹配到視頻流的index
  4. avcodec_find_decoder 根據(jù)視頻流信息的codec_id找到對(duì)應(yīng)的解碼器_
  5. avcodec_open2 使用給定的AVCodec初始化AVCodecContext_
  6. 初始化輸出文件欲逃、解碼AVPacket和AVFrame結(jié)構(gòu)體
  7. av_read_frame 開始一幀一幀讀取
  8. avcodec_send_packet
  9. avcodec_receive_frame
  10. 格式轉(zhuǎn)換 鳞芙、分別寫入YUV文件
  11. Opengl渲染(本篇不涉及牺荠,放到后面單獨(dú)篇學(xué)習(xí)實(shí)踐)
  12. 釋放資源

1.2 關(guān)鍵函數(shù)

下面我們來看下解碼流程中的關(guān)鍵函數(shù)

1. av_register_all
在3.x或者以前的版本在使用ffmpeg的復(fù)用/解復(fù)用器或者編解碼器之前一定要先調(diào)用該函數(shù)。但是4.x之后ffmpeg修了了內(nèi)部實(shí)現(xiàn)糟趾,該函數(shù)可以省略不寫。

2. avformat_open_input

attribute_deprecated int av_open_input_file(AVFormatContext **ic_ptr,constchar *filename,

                       AVInputFormat *fmt,

                       int buf_size,

                       AVFormatParameters *ap);

以輸入方式打開一個(gè)媒體文件,codecs并沒有打開,只讀取了文件的頭信息.

3. avformat_find_stream_info

Read packets of a media file to get stream information

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

獲取多媒體信息

4. avcodec_find_decoder

Find a registered decoder with a matching codec ID

AVCodec *avcodec_find_decoder(enum AVCodecID id);

根據(jù)codecID找到一個(gè)注冊(cè)過的解碼器

5. avcodec_open2

Initialize the AVCodecContext to use the given AVCodec

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

使用給定的AVCodec初始化AVCodecContext_

6. av_read_frame

Return the next frame of a stream.
@return 0 if OK, < 0 on error or end of file

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

讀取一幀數(shù)據(jù)甚牲,讀到的是AVPacket

7. avcodec_send_packet

Supply raw packet data as input to a decoder.
@return 0 on success, otherwise negative error code

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

給解碼器發(fā)送一幀壓縮的AVPacket 數(shù)據(jù)

8. avcodec_receive_frame

Return decoded output data from a decoder.
@return 0 on success

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

接收解碼器解碼的一幀AVFrame數(shù)據(jù)

9. sws_scale

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);

解碼后YUV格式的視頻像素?cái)?shù)據(jù)保存在AVFrame的data 0-3中义郑。但這些像素并不是連續(xù)存儲(chǔ)的,每行有效像素之后存儲(chǔ)了一些無效像素丈钙,經(jīng)過該函數(shù)處理非驮,去掉無效數(shù)據(jù)。否則會(huì)出現(xiàn)花屏雏赦。

10. 資源釋放相關(guān)函數(shù)

    av_packet_unref(packet);

    sws_freeContext(img_convert_ctx);

    fclose(pYUVFile);

    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecContext);
    avformat_close_input(&avFormatContext);

1.3 關(guān)鍵結(jié)構(gòu)體

關(guān)鍵結(jié)構(gòu)體包括AVFormatContext劫笙、AVStream、AVCodecContext星岗、AVCodec填大、AVCodecParameters、AVPacket俏橘、AVFrame等
下面4張圖片來自雷神


AVFormatCotext和AVInputFormat是和封裝格式相關(guān)的結(jié)構(gòu)體

AVStream允华、AVCodecContex、AVCodec是和編解碼相關(guān)的結(jié)構(gòu)體

AVPacket 與 AVFrame

1.4 補(bǔ)充知識(shí)

1. 宏定義define里面的##

宏定義define里面的##可能不太常見,它的含義就是拼接兩個(gè)字符串靴寂,比如
#define Conn(x,y) x##y

那么 int  n = Conn(123,456);  結(jié)果就是n=123456;

2. 文件的打開方式

File * fp = fopen(info.txt,"wb+")

fprintf()
或者fwirte

fclose(fp);

關(guān)于打開方式的說明如下

.  r+ 以可讀寫方式打開文件磷蜀,該文件必須存在。

  rb+ 讀寫打開一個(gè)二進(jìn)制文件百炬,只允許讀寫數(shù)據(jù)褐隆。

  rt+ 讀寫打開一個(gè)文本文件,允許讀和寫剖踊。

  w 打開只寫文件妓灌,若文件存在則文件長度清0,若文件不存在則建立該文件。

  w+ 打開可讀寫文件蜜宪,若文件存在則文件長度清為零虫埂,若文件不存在則建立該文件。

  a 以附加的方式打開只寫文件

  a+ 以附加方式打開可讀寫的文件圃验。若文件不存在掉伏,則會(huì)建立該文件,如果文件存在澳窑,寫入的數(shù)據(jù)會(huì)被加到文件尾后

  wb 只寫打開或新建一個(gè)二進(jìn)制文件斧散;只允許寫數(shù)據(jù)。

  wb+ 讀寫打開或建立一個(gè)二進(jìn)制文件摊聋,允許讀和寫鸡捐。

  wt+ 讀寫打開或著建立一個(gè)文本文件;允許讀寫麻裁。

  at+ 讀寫打開一個(gè)文本文件箍镜,允許讀或在文本末追加數(shù)據(jù)。

  ab+ 讀寫打開一個(gè)二進(jìn)制文件煎源,允許讀或在文件末追加數(shù)據(jù)色迂。

3. YUV數(shù)據(jù)類型

輸出解碼前的h264碼流、輸出解碼后的YUV信息
使用Elecard StreamEye Tools查看輸出的h264數(shù)據(jù)

視頻顯示的流程手销,就是將像素?cái)?shù)據(jù)“畫”在屏幕上的過程歇僧。
例如顯示YUV,就是將YUV“畫”在系統(tǒng)的窗口中锋拖。

YUV 4:4:4采樣诈悍,每一個(gè)Y對(duì)應(yīng)一組UV分量。
YUV 4:2:2采樣兽埃,每兩個(gè)Y共用一組UV分量侥钳。 
YUV 4:2:0采樣,每四個(gè)Y共用一組UV分量讲仰。


 YUV420P慕趴,Y痪蝇,U,V三個(gè)分量都是平面格式冕房,分為I420和YV12躏啰。I420格式和YV12格式的不同處在U平面和V平面的位置不同。在I420格式中耙册,U平面緊跟在Y平面之后给僵,然后才是V平面(即:YUV);但YV12則是相反(即:YVU)详拙。
YUV420SP, Y分量平面格式帝际,UV打包格式, 即NV12。 NV12與NV21類似饶辙,U 和 V 交錯(cuò)排列,不同在于UV順序蹲诀。

I420: YYYYYYYY UU VV    =>YUV420P (最常見的)
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

二、mp4通過FFmpeg解碼成YUV裸數(shù)據(jù)

通過上一小節(jié)弃揽,我們了解了FFmpeg解碼流程和關(guān)鍵的結(jié)構(gòu)體脯爪,這一小節(jié)我們來實(shí)踐。

具體步驟說明和代碼實(shí)現(xiàn)如下:

#include <jni.h>
#include <string>


extern "C" {
#include "include/libavcodec/avcodec.h"
#include "include/libavformat/avformat.h"
#include "include/log.h"
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

}

extern "C"
JNIEXPORT jint JNICALL
Java_android_spport_mylibrary2_Demo_decodeVideo(JNIEnv *env, jobject thiz, jstring inputPath,
                                                jstring outPath) {
    //申請(qǐng)avFormatContext空間矿微,記得要釋放
    AVFormatContext *avFormatContext = avformat_alloc_context();


    const char *url = env->GetStringUTFChars(inputPath, 0);
    //1. 打開媒體文件
    int reuslt = avformat_open_input(&avFormatContext, url, NULL, NULL);
    if (reuslt != 0) {
        LOGE("open input error url=%s, result=%d", url, reuslt);
        return -1;
    }
    //2.讀取媒體文件信息痕慢,給avFormatContext賦值
    if (avformat_find_stream_info(avFormatContext, NULL) < 0) {
        LOGE("find stream error");
        return -1;
    }

    //3. 匹配到視頻流的index
    int videoIndex = -1;
    for (int i = 0; i < avFormatContext->nb_streams; i++) {
        AVMediaType codecType = avFormatContext->streams[i]->codecpar->codec_type;
        LOGI("avcodec type %d", codecType);
        if (AVMEDIA_TYPE_VIDEO == codecType) {
            videoIndex = i;
            break;
        }
    }
    if (videoIndex == -1) {
        LOGE("not find a video stream");
        return -1;
    }

    AVCodecParameters *pCodecParameters = avFormatContext->streams[videoIndex]->codecpar;

    //4. 根據(jù)視頻流信息的codec_id找到對(duì)應(yīng)的解碼器
    AVCodec *pCodec = avcodec_find_decoder(pCodecParameters->codec_id);

    if (pCodec == NULL) {
        LOGE("Couldn`t find Codec");
        return -1;
    }

    AVCodecContext *pCodecContext = avFormatContext->streams[videoIndex]->codec;

    //5.使用給定的AVCodec初始化AVCodecContext
    int openResult = avcodec_open2(pCodecContext, pCodec, NULL);
    if (openResult < 0) {
        LOGE("avcodec open2 result %d", openResult);
        return -1;
    }

    const char *outPathStr = env->GetStringUTFChars(outPath, NULL);

    //6. 初始化輸出文件、解碼AVPacket和AVFrame結(jié)構(gòu)體

    //新建一個(gè)二進(jìn)制文件涌矢,已存在的文件將內(nèi)容清空掖举,允許讀寫
    FILE *pYUVFile = fopen(outPathStr, "wb+");
    if (pYUVFile == NULL) {
        LOGE(" fopen outPut file error");
        return -1;
    }


    auto *packet = (AVPacket *) av_malloc(sizeof(AVPacket));

    //avcodec_receive_frame時(shí)作為參數(shù),獲取到frame娜庇,獲取到的frame有些可能是錯(cuò)誤的要過濾掉塔次,否則相應(yīng)幀可能出現(xiàn)綠屏
    AVFrame *pFrame = av_frame_alloc();
    //作為yuv輸出的frame承載者,會(huì)進(jìn)行縮放和過濾出錯(cuò)的幀思灌,YUV相應(yīng)的數(shù)據(jù)也是從該對(duì)象中讀取
    AVFrame *pFrameYUV = av_frame_alloc();

    //out_buffer中數(shù)據(jù)用于渲染的,且格式為YUV420P
    uint8_t *out_buffer = (unsigned char *) av_malloc(
            av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecContext->width,
                                     pCodecContext->height, 1));

    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
                         AV_PIX_FMT_YUV420P, pCodecContext->width, pCodecContext->height, 1);

    // 由于解碼出來的幀格式不一定是YUV420P的,在渲染之前需要進(jìn)行格式轉(zhuǎn)換
    struct SwsContext *img_convert_ctx = sws_getContext(pCodecContext->width, pCodecContext->height,
                                                        pCodecContext->pix_fmt,
                                                        pCodecContext->width, pCodecContext->height,
                                                        AV_PIX_FMT_YUV420P,
                                                        SWS_BICUBIC, NULL, NULL, NULL);


    int readPackCount = -1;
    int frame_cnt = 0;
    clock_t startTime = clock();

    //7. 開始一幀一幀讀取
    while ((readPackCount = av_read_frame(avFormatContext, packet) >= 0)) {
        LOGI(" read fame count is %d", readPackCount);

        if (packet->stream_index == videoIndex) {
            //8. send AVPacket
            int sendPacket = avcodec_send_packet(pCodecContext, packet);
            //return 0 on success, otherwise negative error code:
            if (sendPacket != 0) {
                LOGE("avodec send packet error %d", sendPacket);
                continue;
            }
            //9. receive frame
            // 0:  success, a frame was returned
            int receiveFrame = avcodec_receive_frame(pCodecContext, pFrame);

            if (receiveFrame != 0) {
                //如果接收到的fame不等于0俺叭,忽略這次receiver否則會(huì)出現(xiàn)綠屏幀
                LOGE("avcodec_receive_frame error %d", receiveFrame);
                continue;
            }
            //10. 格式轉(zhuǎn)換
            sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,
                      0, pCodecContext->height,
                      pFrameYUV->data, pFrameYUV->linesize);

            //11. 分別寫入YUV數(shù)據(jù)
            int y_size = pCodecParameters->width * pCodecParameters->height;
            //YUV420p
            fwrite(pFrameYUV->data[0], 1, y_size, pYUVFile);//Y
            fwrite(pFrameYUV->data[1], 1, y_size / 4, pYUVFile);//U
            fwrite(pFrameYUV->data[2], 1, y_size / 4, pYUVFile);//V

            //輸出I、P泰偿、B幀信息
            char pictypeStr[10] = {0};
            switch (pFrame->pict_type) {
                case AV_PICTURE_TYPE_I: {
                    sprintf(pictypeStr, "I");
                    break;
                }
                case AV_PICTURE_TYPE_P: {
                    sprintf(pictypeStr, "P");
                    break;
                }
                case AV_PICTURE_TYPE_B: {
                    sprintf(pictypeStr, "B");
                    break;
                }
            }
            LOGI("Frame index %5d. Tpye %s", frame_cnt, pictypeStr);
            frame_cnt++;
        }
    }

    LOGI("frame count is %d", frame_cnt);
    clock_t endTime = clock();

    //long類型用%ld輸出
    LOGI("decode video use Time %ld", (endTime - startTime));


    //12.釋放相關(guān)資源

    //釋放packet
    av_packet_unref(packet);

    sws_freeContext(img_convert_ctx);

    fclose(pYUVFile);

    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecContext);
    avformat_close_input(&avFormatContext);
    return 0;

}
}

解碼后的數(shù)據(jù)使用ffplayer進(jìn)行播放。注意參數(shù)設(shè)置, 比如格式和分辨率等

eg:
ffplay /Users/yabin/Desktop/tmp/ffmpeg/output8.yuv -pix_fmt yuv420p -s 784x480

代碼已上傳至 github
https://github.com/ayyb1988/ffmpegvideodecodedemo

三蜈垮、遇到的問題

  1. avformat_open_input -13

原因: 沒有讀寫權(quán)限導(dǎo)致耗跛,-13是權(quán)限相關(guān)的錯(cuò)誤,

在AndroidManifest.xml中加入以下權(quán)限
然后在代碼上添加動(dòng)態(tài)權(quán)限檢查
  1. 生成的yuv導(dǎo)出來后用ffplay或者yuvplayer播放 出現(xiàn)花屏
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 784x480, 338 kb/s, 25 fps, 25 tbr, 16k tbn, 50 tbc (default)

ffplay /Users/yabin/Desktop/tmp/ffmpeg/output.yuv -pix_fmt yuv420p -s 784x480

原因:沒有對(duì)YUV數(shù)據(jù)設(shè)置格式和分辨率等信息

    uint8_t *out_buffer = (unsigned char *) av_malloc(
            av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecContext->width,
                                     pCodecContext->height, 1));

    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
                         AV_PIX_FMT_YUV420P, pCodecContext->width, pCodecContext->height, 1);


    struct SwsContext *img_convert_ctx = sws_getContext(pCodecContext->width, pCodecContext->height,
                                                        pCodecContext->pix_fmt,
                                                        pCodecContext->width, pCodecContext->height,
                                                        AV_PIX_FMT_YUV420P,
                                                        SWS_BICUBIC, NULL, NULL, NULL);

...

    sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,
                      0, pCodecContext->height,
                      pFrameYUV->data, pFrameYUV->linesize);
  1. 第一幀出現(xiàn)綠屏
    原因: 如果接收到的fame不等于0攒发,要忽略這次receiver否則會(huì)出現(xiàn)綠屏幀
 int sendPacket = avcodec_send_packet(pCodecContext, packet);
            //return 0 on success, otherwise negative error code:
            if (sendPacket != 0) {
                LOGE("avodec send packet error %d", sendPacket);
                continue;
            }
            // 0:  success, a frame was returned
            int receiveFrame = avcodec_receive_frame(pCodecContext, pFrame);

            if (receiveFrame != 0) {
                //如果接收到的fame不等于0调塌,忽略這次receiver否則會(huì)出現(xiàn)綠屏幀
                LOGE("avcodec_receive_frame error %d", receiveFrame);
                continue;
            }

四、 資料

  1. 《音視頻開發(fā)進(jìn)階》
  2. FFMPEG中最關(guān)鍵的結(jié)構(gòu)體之間的關(guān)系
  3. ffmpeg函數(shù)介紹
  4. 100行代碼實(shí)現(xiàn)最簡單的基于FFMPEG+SDL的視頻播放器(SDL1.x)
  5. 最簡單的基于FFmpeg的移動(dòng)端例子:Android 視頻解碼器-單個(gè)庫版
  6. 圖文詳解YUV420數(shù)據(jù)格式
  7. ffmpeg flv轉(zhuǎn)MP4 一點(diǎn)心得
  8. FFmpeg編解碼處理1-轉(zhuǎn)碼全流程簡介
  9. FFmpeg源代碼簡單分析:常見結(jié)構(gòu)體的初始化和銷毀(AVFormatContext惠猿,AVFrame等)

測(cè)試視頻來自:FFmpeg編解碼處理1-轉(zhuǎn)碼全流程簡介
下載測(cè)試文件(右鍵另存為):tnmil2.flv

五羔砾、收獲

  1. 了解ffmpeg解碼流程
  2. 了解ffmpeg關(guān)鍵的結(jié)構(gòu)以及之間的關(guān)系
  3. 解碼mp4為視頻裸數(shù)據(jù)YUV
  4. 花屏、錄屏問題分析解決

與Mediacodec解碼對(duì)比姜凄、YUV渲染播放等內(nèi)容董虱,我們后續(xù)章節(jié)來學(xué)習(xí)實(shí)踐捐友。

感謝你的閱讀

下一篇我們學(xué)習(xí)實(shí)踐使用FFmpeg解碼音頻,歡迎關(guān)注公眾號(hào)“音視頻開發(fā)之旅”砌溺,一起學(xué)習(xí)成長匣缘。

歡迎交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖维哈,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谒亦,死亡現(xiàn)場(chǎng)離奇詭異狞甚,居然都是意外死亡谐腰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門址儒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事∈薹撸” “怎么了哲思?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵靠益,是天一觀的道長抱环。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任杭棵,我火速辦了婚禮,結(jié)果婚禮上撩笆,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布矮燎。 她就那樣靜靜地躺著峡谊,像睡著了一般啥纸。 火紅的嫁衣襯著肌膚如雪荣暮。 梳的紋絲不亂的頭發(fā)上迷扇,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天雏胃,我揣著相機(jī)與錄音厂汗,去河邊找鬼。 笑死呜师,一個(gè)胖子當(dāng)著我的面吹牛娶桦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汁汗,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼衷畦,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了知牌?” 一聲冷哼從身側(cè)響起祈争,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎送爸,沒想到半個(gè)月后铛嘱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袭厂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年墨吓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纹磺。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帖烘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橄杨,到底是詐尸還是另有隱情秘症,我是刑警寧澤照卦,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站乡摹,受9級(jí)特大地震影響役耕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聪廉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一瞬痘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧板熊,春花似錦框全、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至容劳,卻和暖如春喘沿,著一層夾襖步出監(jiān)牢的瞬間淆院,已是汗流浹背柴墩。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卓练,地道東北人娶视。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓晒哄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肪获。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寝凌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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