RTMP (五)攝像頭數(shù)據(jù)處理

目錄:

RTMP(一)錄屏直播理論入門
RTMP(二)搭建推流服務
RTMP (三)音視頻采集與數(shù)據(jù)封包
RTMP(四)交叉編譯與CameraX
RTMP (五)攝像頭數(shù)據(jù)處理
RTMP (六)音視頻編碼推流

前面的文章提過哟玷,使用Android進行攝像頭直播,流程

RTMP直播實現(xiàn)流程.png

就圖像而言,首先需要獲得攝像頭采集的數(shù)據(jù)刃鳄,然后得到這個byte[] 進行編碼徐勃,再進行后續(xù)的封包與發(fā)送。我們通 過CameraX圖像分析接口得到的數(shù)據(jù)為ImageProxy(Image的代理類)。那么怎么從ImageProxy/Image 中獲取 我們需要的數(shù)據(jù)呢教寂,這個數(shù)據(jù)格式是什么?

ImageProxy/Image

Image是android SDK提供的一個完整的圖像緩沖區(qū)页慷,圖像數(shù)據(jù)為:YUV或者RGB等格式憔足。在編碼時,一般編碼器接 收的待編碼數(shù)據(jù)格式為 I420差购。而ImageProxy則是CameraX中定義的一個接口四瘫,Image的所有方法,也都能夠從 ImageProxy調用欲逃。

@Override
    public void analyze(ImageProxy image, int rotationDegrees) {
        
        int width = image.getWidth();
        int height = image.getHeight();
        // 格式 YUV/RGB..
        int format = image.getFormat();
        // 圖像數(shù)據(jù)
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        
        
        
        
        byte[] bytes = ImageUtils.getBytes(image, rotationDegrees, rtmpClient.getWidth(), rtmpClient.getHeight());
        try {
            if (fos != null)
                fos.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

可以通過 getPlanes方法得到PlaneProxy數(shù)組找蜜。PlaneProxy為Image.Plane代理,同ImagePrxoy與Image的關系 一樣稳析。

planes0_bytebuffer.png

其實CameraX給到我們的數(shù)據(jù)格式在官網(wǎng)中有提到:CameraX會生成YUV_420_888格式圖片

YUV420

YUV即通過Y洗做、U和V三個分量表示顏色空間,其中Y表示亮度彰居,U和V表示色度诚纸。( 如果UV數(shù)據(jù)都為0,那么我們將得到一個黑白的圖像)

RGB中每個像素點都有獨立的R陈惰、G和B三個顏色分量值畦徘,YUV根據(jù)U和V采樣數(shù)目的不同,分為如YUV444抬闯、 YUV422和YUV420等井辆,而YUV420表示的就是每個像素點有一個獨立的亮度表示,即Y分量;而色度溶握,即U和V分量 則由每4個像素點共享一個杯缺。舉例來說,對于4x4的圖片睡榆,在YUV420下萍肆,有16個Y值,4個U值和4個V值胀屿。

YUV420根據(jù)顏色數(shù)據(jù)的存儲順序不同塘揣,又分為了多種不同的格式,這些格式實際存儲的信息還是完全一致的碉纳。舉 例來說勿负,對于4x4的圖片,在YUV420下,任何格式都有16個Y值奴愉,4個U值和4個V值琅摩,不同格式只是Y、U和V的排 列順序變化锭硼。I420 為 YYYYYYYYYYYYYYYYUUUUVVVV 房资,NV21 則為 YYYYYYYYYYYYYYYYUVUVUVUV 。也就是說檀头,YUV420 是一類格式的集合轰异,YUV420并不能完全確定顏色數(shù)據(jù)的存儲順序。

為了方便觀看暑始,我們給他們變個形狀搭独,便于理解

I420 : 4x4 ----> 4x4+4/2*4/2 + 4/2*4/2 = 4 * 4 * 3 / 2

Y  Y  Y  Y
Y  Y  Y  Y
Y  Y  Y  Y
Y  Y  Y  Y
U  U
U  U
V  V
V  V

NV21

Y  Y  Y  Y
Y  Y  Y  Y
Y  Y  Y  Y
Y  Y  Y  Y
U  V  U  V
U  V  U  V

PlaneProxy/Plane

Y、U和V三個分量的數(shù)據(jù)分別保存在三個 Plane類中廊镜,即通過 getPlanes()得到的數(shù)組牙肝。 Plane 實際是對
ByteBuffer的封裝。

Image保證了planes[0]一定是Y嗤朴,planes[1]一定是U配椭,planes[2]一定是V。且對于plane [0]雹姊,Y分量數(shù)據(jù)一定是連續(xù)存儲的股缸,中間不會有U或V數(shù)據(jù)穿插,也就是說我們一定能夠一次性得到所有Y分量的值吱雏。

但是對于UV數(shù)據(jù)敦姻,可能存在以下兩種情況:

1. planes[1] = {UUUU...},planes[2] = {VVVV...}; 
2. planes[1] = {UVUV...}歧杏,planes[2] = {VUVU...}替劈。

所以在我么取數(shù)據(jù)時需要在根據(jù)Plane中的另一個信息來確定如何取對應的U或者V數(shù)據(jù)。

// 行內數(shù)據(jù)值間隔
// 1:表示無間隔取值得滤,即為上面的第一種情況
// 2: 表示需要間隔一個數(shù)據(jù)取值,即為上面的第二種情況
 int pixelStride = plane.getPixelStride();

根據(jù)這個屬性盒犹,我們將確定數(shù)據(jù)如何存儲懂更,因此如果需要取出代表I420格式的byte[],則為:
YUV420中急膀,Y數(shù)據(jù)長度為: width*height , 而U沮协、V都為:width / 2 * height / 2。

很容易回想到

// Y數(shù)據(jù) pixelStride一定為1
        int pixelStride = planes[0].getPixelStride();
        planes[0].getBuffer() // Y數(shù)據(jù)
        byte[] u = new byte[image.getWidth() / 2 * image.getHeight() / 2];
        int pixelStride = planes[1].getPixelStride();
        if (pixelStide == 1) {
            planes[1].getBuffer() // U數(shù)據(jù)
        } else if (pixelStide == 2) {
            ByteBuffer uBuffer = planes[1].getBuffer()
            for (int i = 0; i < uBuffer.remaining(); i+=2) {
                u[i] = uBuffer.get(); //丟棄一個數(shù)據(jù)卓嫂,這個數(shù)據(jù)其實是V數(shù)據(jù)慷暂,但是我們還是到planes[2]中獲取V數(shù)據(jù) 
                uBuffer.get();
            }
        }

但是如果使用上面的代碼去獲取I420數(shù)據(jù),可能你會驚奇的發(fā)現(xiàn),并不是在所有你設置的Width與 Height(分辨率)下都能夠正常運行行瑞。我們忽略了什么奸腺,為什么會出現(xiàn)問題呢?
在Plane中 我們已經(jīng)使用了 getBuffer 與 getPixelStride 兩個方法,但是還有一個 getRowStride沒有用到

RowStride

RowStride表示行步長血久,Y數(shù)據(jù)對應的行步長可能為:

  1. 等于Width;
  2. 大于Width;
    以4x4的I420為例突照,其數(shù)據(jù)可以看為


    y4202.png

如果RowStride等于Width,那么我們直接通過 planes[0].getBuffer() 獲得Y數(shù)據(jù)沒有問題氧吐。

但是如果RowStride大于Width讹蘑,比如對于4x4的I420,如果每行需要以8字節(jié)對齊筑舅,那么可能得到的RowStride不
等于4(Width)座慰,而是得到8。那么此時會在每行數(shù)據(jù)末尾補充占位的無效數(shù)據(jù):

y4201.png

對于這種情況翠拣,我們獲取Y數(shù)據(jù)版仔,則為:

//用于保存獲取的I420數(shù)據(jù)。大小為:y+u+v, width*height + width/2*height/2 + width/2*height/2
        ByteBuffer i420 = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);
        // 3個元素 0:Y心剥,1:U邦尊,2:V
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        // byte[]

        /**
         * Y數(shù)據(jù)
         */
        //y數(shù)據(jù)的這個值只能是:1
        int pixelStride = planes[0].getPixelStride();
        ByteBuffer yBuffer = planes[0].getBuffer();
        int rowStride = planes[0].getRowStride();

// 每行要排除的無效數(shù)據(jù),但是需要注意:實際測試中 最后一行沒有這個補位數(shù)據(jù)
 // 因為Y數(shù)據(jù) RowStride 為大于等于Width优烧,所以不會出現(xiàn)負數(shù)導致錯誤
// RowStride 等于Width蝉揍,則得到空數(shù)組,不丟棄數(shù)據(jù)
        //1畦娄、rowStride 等于Width 又沾,那么就是一個空數(shù)組
        //2、rowStride 大于Width 熙卡,那么就是每行多出來的數(shù)據(jù)大小個byte
        byte[] skipRow = new byte[rowStride - image.getWidth()];
        byte[] row = new byte[image.getWidth()];
        for (int i = 0; i < image.getHeight(); i++) {
            yBuffer.get(row);
            i420.put(row);
            // 不是最后一行才有無效占位數(shù)據(jù)杖刷,最后一行因為后面跟著U 數(shù)據(jù),沒有無效占位數(shù)據(jù)驳癌,不需要丟棄
            if (i < image.getHeight() - 1) {
                yBuffer.get(skipRow);
            }
        }

而對于U與V數(shù)據(jù)滑燃,對應的行步長可能為:

  1. 等于Width;
  2. 大于Width;
  3. 等于Width/2;
  4. 大于Width/2

等于Width

這表示,我們獲得planes[1]中不僅包含U數(shù)據(jù)颓鲜,還會包含V的數(shù)據(jù)表窘,此時pixelStride==2。

U V U V
U V U V

那么V數(shù)據(jù):planes[2]甜滨,則為:

V U V U
V U V U

大于Width
與Y數(shù)據(jù)一樣乐严,可能由于字節(jié)對齊,出現(xiàn)RowStride大于Width的情況衣摩,與等于Width一樣昂验,planes[1]中不僅包含U 數(shù)據(jù),還會包含V的數(shù)據(jù),此時pixelStride==2既琴。

U V U V 0 0 0 0
U V U V 最后一行沒有站位

planes[2]占婉,則為:

V U V U 0 0 0 0
V U V U 最后一行沒有站位

等于Width/2
當獲取的U數(shù)據(jù)對應的RowStride等于Width/2,表示我們得到的planes[1]只包含U數(shù)據(jù)呛梆。此時pixelStride==1锐涯。 那么planes[1]+planes[2]為:

U U
U U
V V
V V

這種情況,所有的U數(shù)據(jù)是連在一起的填物,即 planes[1].getBuffer 可以直接獲得完整的U數(shù)據(jù)纹腌。

大于Width/2

同樣我們得到的planes[1]只包含U數(shù)據(jù),但是與Y數(shù)據(jù)一樣滞磺,可能存在占位數(shù)據(jù)升薯。此時pixelStride==1。 planes[1]+planes[2]為:

U U 0 0 0 0 0 0
U U 最后一行沒有站位
V V 0 0 0 0 0 0
V V 最后一行沒有站位

綜上得出

在獲得了攝像頭采集的數(shù)據(jù)之后击困,我們需要獲取對應的YUV數(shù)據(jù)涎劈,需要根據(jù)pixelStride判斷格式,同時還需要通過 rowStride來確定是否存在無效數(shù)據(jù)阅茶,那么最終我們獲取YUV數(shù)據(jù)的完整實現(xiàn)為:

public static byte[] getBytes(ImageProxy image, int rotationDegrees, int width, int height) {
        //圖像格式
        int format = image.getFormat();
        if (format != ImageFormat.YUV_420_888) {
            //拋出異常
        }
        //用于保存獲取的I420數(shù)據(jù)蛛枚。大小為:y+u+v, width*height + width/2*height/2 + width/2*height/2
        ByteBuffer i420 = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);
        // 3個元素 0:Y,1:U脸哀,2:V
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        // byte[]

        /**
         * Y數(shù)據(jù)
         */
        //y數(shù)據(jù)的這個值只能是:1
        int pixelStride = planes[0].getPixelStride();
        ByteBuffer yBuffer = planes[0].getBuffer();
        int rowStride = planes[0].getRowStride();

        //1蹦浦、rowStride 等于Width ,那么就是一個空數(shù)組
        //2撞蜂、rowStride 大于Width 盲镶,那么就是每行多出來的數(shù)據(jù)大小個byte
        byte[] skipRow = new byte[rowStride - image.getWidth()];
        byte[] row = new byte[image.getWidth()];
        for (int i = 0; i < image.getHeight(); i++) {
            yBuffer.get(row);
            i420.put(row);
            // 不是最后一行才有無效占位數(shù)據(jù),最后一行因為后面跟著U 數(shù)據(jù)蝌诡,沒有無效占位數(shù)據(jù)溉贿,不需要丟棄
            if (i < image.getHeight() - 1) {
                yBuffer.get(skipRow);
            }
        }

        /**
         * U、V
         */
        for (int i = 1; i < 3; i++) {
            ImageProxy.PlaneProxy plane = planes[i];
            pixelStride = plane.getPixelStride();
            rowStride = plane.getRowStride();
            ByteBuffer buffer = plane.getBuffer();

            //每次處理一行數(shù)據(jù)
            int uvWidth = image.getWidth() / 2;
            int uvHeight = image.getHeight() / 2;

            // 一次處理一個字節(jié)
            for (int j = 0; j < uvHeight; j++) {
                for (int k = 0; k < rowStride; k++) {
                    //最后一行
                    if (j == uvHeight - 1) {
                        //uv沒混合在一起
                        if (pixelStride == 1) {
                            //rowStride :大于等于Width/2
                            // 結合外面的if:
                            //  如果是最后一行浦旱,我們就不管結尾的占位數(shù)據(jù)了
                            if (k >= uvWidth) {
                                break;
                            }
                        } else if (pixelStride == 2) {
                            //uv混在了一起
                            // rowStride:大于等于 Width
                            if (k >= image.getWidth()) {
                                break;
                            }
                        }
                    }


                    byte b = buffer.get();
                    // uv沒有混合在一起
                    if (pixelStride == 1) {
                        if (k < uvWidth) {
                            i420.put(b);
                        }
                    } else if (pixelStride == 2) {
                        // uv混合在一起了
                        //1宇色、偶數(shù)位下標的數(shù)據(jù)是我們本次要獲得的U/V數(shù)據(jù)
                        //2、占位無效數(shù)據(jù)要丟棄颁湖,不保存
                        if (k < image.getWidth() && k % 2 == 0) {
                            i420.put(b);
                        }
                    }
                }
            }
        }

代兵。。爷狈。
        return result;
    }

旋轉與縮放

YUV數(shù)據(jù)旋轉

yuv旋轉.png

分別對Y、U裳擎、V進行旋轉即可涎永。 無論是旋轉還是縮放我們都能借助一些開源實現(xiàn)來完成,如OpenCV、Libyuv等羡微。這里我們選擇使用Libyuv谷饿,它更加的輕量級同時也是專門處理各種圖像數(shù)據(jù)的格式轉換、縮放與旋轉等的Google開源的C++圖像處理庫.

對于CameraX獲得的圖像數(shù)據(jù)妈倔,我們從ImageProxy中獲得I420之后博投,還需要進行旋轉。需要旋轉的角度在回調中已經(jīng)告知我們 public void analyze(ImageProxy image, int rotationDegrees) 盯蝴。

同時作為直播推流器的開發(fā)毅哗,使用者可以配置各種分辨率,不一定符合CameraX得到的分辨率捧挺。所以我們在對圖像 旋轉后再對他進行縮放至使用者配置的推流分辨率大小虑绵。

。闽烙。翅睛。
int srcWidth = image.getWidth();
        int srcHeight = image.getHeight();
        //I420
        byte[] result = yuv420.array();

        if (rotationDegrees == 90 || rotationDegrees == 270) {
            //旋轉之后 ,圖像寬高交換
            // TODO: 2020/11/1 result 修改值黑竞,避免內存抖動
            rotation(result, image.getWidth(), image.getHeight(), rotationDegrees);
            srcWidth = image.getHeight();
            srcHeight = image.getWidth();
        }

        if(srcWidth != width || srcHeight != height){
            // TODO: 2020/11/1 jni對scalBytes 修改值捕发,避免內存抖動
            int scaleSize = width * height * 3 /2;
            if(scaleBytes == null || scaleBytes.length < scaleSize) {
                scaleBytes = new byte[scaleSize];
            }
            scale(result,scaleBytes,srcWidth,srcHeight,width,height);
            return scaleBytes;
        }


。很魂。扎酷。
private static native void rotation(byte[] data, int width, int height, int degress);

    private static native void scale(byte[] src,byte[] dst,int srcWidth,int srcHeight,int dstWidth,int dstHeight);


首先從官網(wǎng)下載Libyuv源碼 :https://chromium.googlesource.com/libyuv/libyuv 。并按照之前學習 的內容莫换,可以選擇將其整個源碼放入AS中一起編譯霞玄。
對應的旋轉與縮放實現(xiàn)為:

#include <jni.h>
#include <libyuv.h>

extern "C"
JNIEXPORT void

JNICALL
Java_top_zcwfeng_pusher_ImageUtils_rotation(JNIEnv *env, jclass clazz, jbyteArray data_,
                                            jint width, jint height, jint degress) {

    jbyte *data = env->GetByteArrayElements(data_, 0);
    uint8_t *src = reinterpret_cast<uint8_t *>(data);
    int ySize = width * height;
    int uSize = (width >> 1) * (height >> 1);
    int size = (ySize * 3) >> 1;
    uint8_t *src_y = src;
    uint8_t *src_u = src + ySize;
    uint8_t *src_v = src + ySize + uSize;

    uint8_t dst[size];
    uint8_t *dst_y = dst;
    uint8_t *dst_u = dst + ySize;
    uint8_t *dst_v = dst + ySize + uSize;
    libyuv::I420Rotate(src_y, width, src_u, width >> 1, src_v, width >> 1,
                       dst_y, height, dst_u, height >> 1, dst_v, height >> 1,
                       width, height, static_cast<libyuv::RotationMode>(degress));


    jbyteArray result = env->NewByteArray(size);
    env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte *>(dst));

    env->ReleaseByteArrayElements(data_, data, 0);
    env->SetByteArrayRegion(data_, 0, size, reinterpret_cast<const jbyte *>(dst));

}
extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_pusher_ImageUtils_scale(JNIEnv *env, jclass clazz, jbyteArray src_,
                                         jbyteArray dst_,
                                         jint src_width, jint src_height, jint dst_width,
                                         jint dst_height) {
    jbyte *data = env->GetByteArrayElements(src_, 0);
    uint8_t *src = reinterpret_cast<uint8_t *>(data);
    int64_t size = (dst_width * dst_height * 3) >> 1;
    uint8_t dst[size];


    uint8_t *src_y;
    int src_stride_y;
    uint8_t *src_u;
    int src_stride_u;
    uint8_t *src_v;
    int src_stride_v;

    uint8_t *dst_y;
    int dst_stride_y;
    uint8_t *dst_u;
    int dst_stride_u;
    uint8_t *dst_v;
    int dst_stride_v;

    src_stride_y = src_width;
    src_stride_u = src_width >> 1;
    src_stride_v = src_stride_u;

    dst_stride_y = dst_width;
    dst_stride_u = dst_width >> 1;
    dst_stride_v = dst_stride_u;


    int src_y_size = src_width * src_height;
    int src_u_size = src_stride_u * (src_height >> 1);
    src_y = src;
    src_u = src + src_y_size;
    src_v = src + src_y_size + src_u_size;

    int dst_y_size = dst_width * dst_height;
    int dst_u_size = dst_stride_u * (dst_height >> 1);
    dst_y = dst;
    dst_u = dst + dst_y_size;
    dst_v = dst + dst_y_size + dst_u_size;


    libyuv::I420Scale(src_y, src_stride_y,
                      src_u, src_stride_u,
                      src_v, src_stride_v,
                      src_width, src_height,
                      dst_y, dst_stride_y,
                      dst_u, dst_stride_u,
                      dst_v, dst_stride_v,
                      dst_width, dst_height,
                      libyuv::FilterMode::kFilterNone
    );


    env->ReleaseByteArrayElements(src_, data, 0);
    env->SetByteArrayRegion(dst_, 0, size, reinterpret_cast<const jbyte *>(dst));
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者拉岁。
  • 序言:七十年代末坷剧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喊暖,更是在濱河造成了極大的恐慌惫企,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陵叽,死亡現(xiàn)場離奇詭異狞尔,居然都是意外死亡,警方通過查閱死者的電腦和手機巩掺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門偏序,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胖替,你說我怎么就攤上這事研儒≡ビВ” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵端朵,是天一觀的道長好芭。 經(jīng)常有香客問我,道長冲呢,這世上最難降的妖魔是什么舍败? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮敬拓,結果婚禮上邻薯,老公的妹妹穿的比我還像新娘。我一直安慰自己恩尾,他們只是感情好弛说,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翰意,像睡著了一般木人。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冀偶,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天醒第,我揣著相機與錄音,去河邊找鬼进鸠。 笑死稠曼,一個胖子當著我的面吹牛,可吹牛的內容都是我干的客年。 我是一名探鬼主播霞幅,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼量瓜!你這毒婦竟也來了司恳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绍傲,失蹤者是張志新(化名)和其女友劉穎扔傅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烫饼,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡猎塞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杠纵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荠耽。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖比藻,靈堂內的尸體忽然破棺而出铝量,到底是詐尸還是另有隱情伊履,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布款违,位于F島的核電站,受9級特大地震影響群凶,放射性物質發(fā)生泄漏插爹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一请梢、第九天 我趴在偏房一處隱蔽的房頂上張望赠尾。 院中可真熱鬧,春花似錦毅弧、人聲如沸气嫁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸宵。三九已至,卻和暖如春元咙,著一層夾襖步出監(jiān)牢的瞬間梯影,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工庶香, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甲棍,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓赶掖,卻偏偏與公主長得像感猛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奢赂,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359