精品秘无码一区二区三区老师-精品秘一区二三区免费雷安-精品蜜桃秘一区二区三区-精品蜜桃秘一区二区三区粉嫩-精品蜜桃一区二区三区-精品蜜臀国产aⅴ一区二区三区

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

用.NET IoT庫控制舵機并多方法播放表情

freeflydom
2025年2月6日 10:26 本文熱度 81

前言

前面兩篇文章講了.NET IoT相關的知識點,以及硬件的GPIO的一些概念,還有點亮兩個屏幕的方法,這些讓大家對.NET的用途有了新的認識,那我們這回繼續講解.NET IoT的知識點,以及介紹一些好玩的東西,例如讓視頻通過機器人的屏幕播放起來,還有機器人的身體也能通過我們的代碼控制動起來。大家感興趣的話可以跟著我的文章繼續下去,另外說下我B站更新了機器人相關視頻,所以大家可以跟著觀看制作,視頻包含了機器人的組裝和打印文件使用,點擊圖片即可跳轉。

問題解答

大家看完這篇文章,大概對機器人的一些功能模塊有了了解,大家肯定會有疑問,做這個機器人到底需要什么電路板,以及只用樹莓派到底能夠做到什么程度,我會挑一些大家可能會問的問題做一些解答。

1. 只用樹莓派可以控制舵機嗎?

只用樹莓派控制舵機是OK,舵機本身是使用PWM的信號進行控制的,這個可以通過樹莓派的引腳進行模擬,這個不在本文章的討論范圍內,有需要可以單獨寫一篇文章進行講解。

2. 機器人的制作到底需要哪些電路板?

下圖為完整的硬件相關的部分,大家可以大概的了解到機器人的電路構成。

目前機器人總共需要三塊板子,一塊是我設計的搭配樹莓派使用的,另外兩塊是使用的ElectronBot精英版A2的一個舵機驅動板(用來改裝舵機并且驅動舵機的運動),一個語音板子(包含麥克風,喇叭,和攝像頭連接),這些大家都可以通過在閑魚之類搜索ElectronBot相關的關鍵詞買到,大家不要懼怕自己不會焊接電路板不能學習之類。即使大家買不到電路板,通過文章進行學習也是問題不大的,所以大家不要擔心。

3. 如果想學習應該怎么樣獲得電路板?

這個現在網絡上都有一站式創客電路板生產的平臺,例如嘉立創(這個非廣告因為這個是國內算是很成熟的平臺了),我剛才提到的ElectronBot精英版A2和我的樹莓派拓展板子都在立創的開源廣場有提供,大家直接跟著下單就能夠拿到電路板了,然后就可以購買芯片物料焊接了。

4. ElectronBot和我做的機器人有什么關系?

ElectronBot是稚暉君(B站一個有名的UP主)制作的一個開源的必須連接電腦的桌面機器人,我和網友在他的方案基礎上優化了電路板出了一個ElectronBot精英版A2的版本,現在我通過用樹莓派替換了ElectronBot的屏幕控制和舵機控制部分,實現了一個獨立的版本,我為了省事,就借用了ElectronBot的兩個電路板,省的自己設計了。

名詞解釋

1. 什么是舵機?

舵機是一種位置(角度)伺服的驅動器,適用于那些需要角度不斷變化并可以保持的控制系統。舵機通過一瞬間的堵轉扭力將舵盤進行轉向,持續的時間短。最早以前在高檔遙控玩具,如飛機、潛艇模型,遙控機器人中已經得到了普遍應用。如今通過技術的革新,加工工藝的升級,舵機在各行各業中已得到越來越廣泛的應用。

2. 什么是I2C通訊?

I2C(Inter-Integrated Circuit),讀作:I方C,是一種同步、多主多從架構、雙向雙線的串行通信總線,通常應用于短距離、低速通信場景,廣泛用于微控制器和各種外圍設備之間的通信。它使用兩條線路:串行數據線(SDA)和串行時鐘線(SCL)進行雙向傳輸。

3. 什么是lottie動畫?

Lottie 是一種輕量級的基于 JSON 的動畫格式,可以在任何設備或瀏覽器上播放。設計師和開發人員廣泛使用它來改善網站和應用程序的交互。Lottie 的矢量結構允許用戶在不失去圖像質量或增加文件大小的情況下縮放動畫。

4. 什么是ffmpeg?

FFmpeg 是一個完整的跨平臺音視頻解決方案,用于記錄、轉換和流式處理音視頻。它是目前最強大的音視頻處理開源軟件之一,被廣泛應用于視頻網站、播放器、編碼器等多種場景中。

舵機控制

舵機控制板固件相關介紹

  1. 首先我們象征性的看下舵機板子的固件代碼,舵機控制板使用STM32F103標準庫硬件IIC+DMA的類似方案進行數據讀寫,有社區的人進行了優化,但是核心代碼大體相同,改裝的舵機板比舵機原始的只支持角度控制有更多的玩法。參考如下文檔:
    STM32F103標準庫硬件IIC+DMA連續數據發送、接收
    ,核心代碼如下:
    // // Command handler
    void I2C_SlaveDMARxCpltCallback()
    {
        ErrorStatus state;
        float valF = *((float*) (i2cDataRx + 1));
        i2cDataTx[0] = i2cDataRx[0];
        switch (i2cDataRx[0])
        {
            case 0x01:  // Set angle
            {
                motor.dce.setPointPos = valF;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x02: // Set velocity
            {
                motor.dce.setPointVel = valF;
                auto* b = (unsigned char*) &(motor.velocity);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x03: // Set torque
            {
                motor.SetTorqueLimit(valF);
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x11: // Get angle
            {
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x12: // Get velocity
            {
                auto* b = (unsigned char*) &(motor.velocity);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x21: // Set id
            {
                boardConfig.nodeId = i2cDataRx[1];
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x22: // Set kp
            {
                motor.dce.kp = valF;
                boardConfig.dceKp = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x23: // Set ki
            {
                motor.dce.ki = valF;
                boardConfig.dceKi = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x24: // Set kv
            {
                motor.dce.kv = valF;
                boardConfig.dceKv = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x25: // Set kd
            {
                motor.dce.kd = valF;
                boardConfig.dceKd = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x26: // Set torque limit
            {
                motor.SetTorqueLimit(valF);
                boardConfig.toqueLimit = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x27: // Set init pos
            {
                boardConfig.initPos = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0xff:
                motor.SetEnable(i2cDataRx[1] != 0);
                break;
            default:
                break;
        }
        do
        {
        state = Slave_Transmit(i2cDataTx,5,5000);
        } while (state != SUCCESS);
        if(i2cDataRx[0] == 0x21)
        {
            Set_ID(boardConfig.nodeId);
        }
    }
    // Control loop
    void TIM14_PeriodElapsedCallback()
    {
            // Read sensor data
        LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);  
        LL_ADC_REG_StartConversion(ADC1);
        
        motor.angle = motor.mechanicalAngleMin +
                        (motor.mechanicalAngleMax - motor.mechanicalAngleMin) *
                        ((float) adcData[0] - (float) motor.adcValAtAngleMin) /
                        ((float) motor.adcValAtAngleMax - (float) motor.adcValAtAngleMin);
        // Calculate PID
        motor.CalcDceOutput(motor.angle, 0);
        motor.SetPwm((int16_t) motor.dce.output);
    }
    
  2. 固件控制指令對照圖,這些指令是通過樹莓派I2C引腳進行發送。
  3. 個人的一些心得,控制板核心邏輯有個死循環,如果通訊不正常,會一直等待,所以如果樹莓派的執行控制代碼發送的不對,會出現I2C引腳超時的錯誤,這個大家操作的時候一定要記住接線是否正確,代碼是否配置OK。
  4. I2C設備都是并聯到I2C總線上的,每個設備都有一個設備的ID,所以我們在和設備通訊的時候一定要指定設備的ID才能完成初始化。

舵機控制代碼編寫

由于我做的獨立版桌面機器人目前只用到了兩個舵機,所以我選擇了2號和3號ID的舵機進行控制。通過初始化I2C設備對象,進行通訊的建立,并進行角度的控制。示例代碼是將舵機循環往復的運動180°,使用.NET IoT庫編寫,并在樹莓派上部署使用,示例代碼如下:

using System.Device.I2c;
try
{
    while (true)
    {
        using I2cDevice i2c = I2cDevice.Create(new I2cConnectionSettings(1, 0x02));
        using I2cDevice i2c8 = I2cDevice.Create(new I2cConnectionSettings(1, 0x03));
        byte[] writeBuffer = new byte[5] { 0xff, 0x01, 0x00, 0x00, 0x00 };
        byte[] receiveData = new byte[5];
        i2c.WriteRead(writeBuffer, receiveData);
        i2c8.WriteRead(writeBuffer, receiveData);
        for (int i = 0; i < 180; i += 1)
        {
            float angle = i;
            byte[] angleBytes = BitConverter.GetBytes(angle);
            writeBuffer[0] = 0x01;
            Array.Copy(angleBytes, 0, writeBuffer, 1, angleBytes.Length);
            i2c.WriteRead(writeBuffer, receiveData);
            i2c8.WriteRead(writeBuffer, receiveData);
            Thread.Sleep(20);
        }
        for (int i = 180; i > 0; i -= 1)
        {
            float angle = i;
            byte[] angleBytes = BitConverter.GetBytes(angle);
            writeBuffer[0] = 0x01;
            Array.Copy(angleBytes, 0, writeBuffer, 1, angleBytes.Length);
           i2c.WriteRead(writeBuffer, receiveData);
            i2c8.WriteRead(writeBuffer, receiveData);
            Thread.Sleep(20);
        }
        Console.WriteLine($"I2C 2 8 設備連接成功--{DateTime.Now.ToString("s")}");
        foreach (var data in receiveData)
        {
            Console.Write($"{data}, ");
        }
        //Console.WriteLine();
        //Thread.Sleep(500);      
    }
}
catch (Exception ex)
{
    Console.WriteLine($"I2C 設備連接失敗: {ex.Message}");
}
Console.ReadLine();

控制代碼看起來很簡單,但是這里有個坑,就是大家也看到了一個奇怪的地方,就是為什么發送數據的時候要用WriteRead這個方法,而不是先write再Read這樣的操作。其實這里也卡住我了,我翻了固件的源碼,我懷疑是因為舵機版子的速度太快了,導致讀寫的區分不大,如果我只是寫入數據再讀取會導致循環卡住,這里我是推測,我翻了.NET IoT的這個I2C通訊的源碼,然后我用了WriteRead這個方法測試,發現通訊是OK的,如果有大佬能給出更詳細的解答,歡迎評論區給大家科普一下。到這里舵機的控制就算是完成了,具體更詳細的控制大家可以根據控制指令手冊進行編寫測試。

舵機測試

下圖標出了樹莓派的I2C引腳位置,這兩個引腳和舵機控制板的I2C引腳進行接線就可以通訊了,舵機板子需要供電,而且舵機板子的地線要和樹莓派板子共地,如果是其他的I2C設備也是一樣,例如陀螺儀,I2C屏幕。

如果接線OK,代碼運行OK,正常情況下會看到舵機旋轉的樣子。

看到這里大家有什么疑問可以在評論區討論。

多種方式播放表情

這篇文章的篇幅有點長,上面我們講了舵機的控制,上一篇文章我們調通了屏幕的顯示,但是只顯示圖片其實不夠生動的,如果我們能夠配上表情的播放那就生動多了。

解析lottie動畫文件進行播放

上面的名詞解釋我們解釋了什么是lottie動畫,那我們就直接看代碼吧,這個lottie動畫目前我在樹莓派上進行解析不是很流暢,所以只是作為知識講解,大家如果是樹莓派4或者5應該性能很好,解析起來應該不費勁,而且如果代碼能夠優化一些應該也可以流暢。

我的做法是通過使用一些解析庫,能夠解析lottie動畫,提取出幀數據,然后解析成ImageSharp的Image類,然后轉換成字節數組就可以進行播放了。下面是我找到的社區的一些開源庫,SkiaSharp.Skottie有提供解析功能。

	<ItemGroup>
		<PackageReference Include="SkiaSharp" Version="3.116.1" />
		<PackageReference Include="SkiaSharp.Skottie" Version="3.116.1" />
		<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
	</ItemGroup>

核心的解析動畫并轉成Image的代碼如下:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
using SkiaSharp.Skottie;
namespace Verdure.LottieToImage;
public class LottieToImage
{
    public static Image<Bgra32> RenderLottieFrame(Animation animation, double progress, int width, int height)
    {
        // 創建SKSurface用于渲染
        using var bitmap = new SKBitmap(width, height);
        using var canvas = new SKCanvas(bitmap);
        // 清除背景
        canvas.Clear(SKColors.Transparent);
        animation.SeekFrameTime(progress);
        animation.Render(canvas, new SKRect(0, 0, width, height));
        // 將SKBitmap轉換為byte數組
        using var image = SKImage.FromBitmap(bitmap);
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        var bytes = data.ToArray();
        // 轉換為ImageSharp格式
        using var memStream = new MemoryStream(bytes);
        return Image.Load<Bgra32>(memStream);
    }
    public static async Task SaveLottieFramesAsync(string lottieJsonPath, string outputDir, int width, int height)
    {
        Directory.CreateDirectory(outputDir);
        // 讀取Lottie JSON文件
        var animation = Animation.Create(lottieJsonPath);
        if (animation != null)
        {
            //幀數
            var frameCount = animation.OutPoint;
            for (int i = 0; i < frameCount; i++)
            {
                var progress = animation.Duration.TotalSeconds / (frameCount - i);
                var frame = RenderLottieFrame(animation, progress, width, height);
                await frame.SaveAsPngAsync(Path.Combine(outputDir, $"frame_{i:D4}.png"));
            }
        }
    }
}

轉成Image對象之后,就可以使用我們上一篇文章里的方法轉成字節數組寫入到屏幕了。這個大家有興趣可以查看我的項目代碼里,有做demo測試。

桌面桌面機器人倉庫地址

通過轉換MP4格式文件進行播放

這一種方式我是事先通過ffmpeg解析mp4的表情文件,然后將表情轉換成屏幕直接顯示的字節數組,并且序列化到json文件里,這樣將解析轉換的部分的邏輯前置處理了,樹莓派在播放表情的時候就可以很輕松了。
核心轉換代碼邏輯如下:

將視頻幀轉成圖片的字節數組代碼:

using FFmpeg.NET;
using FFmpegImageSharp.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace FFmpegImageSharp.Services;
public class FrameExtractor
{
    public async Task<List<FrameData>> ExtractFramesAsync(string filePath)
    {
        var frames = new List<FrameData>();
        var ffmpeg = new Engine("C:\\ffmpeg-n7.1-latest-win64-gpl-7.1\\ffmpeg-n7.1-latest-win64-gpl-7.1\\bin\\ffmpeg.exe"); // Specify the path to ffmpeg executable
        var mediaFile = new InputFile(filePath); // Use a concrete class instead of MediaFile
        var mediaInfo = await ffmpeg.GetMetaDataAsync(mediaFile, CancellationToken.None);
        var duration = mediaInfo.Duration;
        var frameRate = mediaInfo.VideoData.Fps;
        var frameCount = (int)(duration.TotalSeconds * frameRate);
        for (var i = 0; i < frameCount; i++)
        {
            var timestamp = TimeSpan.FromSeconds(i / frameRate);
            var outputFilePath = $"frame_{i}.jpg";
            var arguments = $"-i \"{filePath}\" -vf \"select='eq(n\\,{i})'\" -vsync vfr -q:v 2 \"{outputFilePath}\"";
            await ffmpeg.ExecuteAsync(arguments, CancellationToken.None);
            var frameImage = await File.ReadAllBytesAsync(outputFilePath);
            var frameData = new FrameData
            {
                ImageData = frameImage,
                Timestamp = timestamp
            };
            frames.Add(frameData);
        }
        return frames;
    }
}

將圖片字節數組轉成顯示屏需要的字節數組數據的代碼如下:

using FFmpegImageSharp.Models;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace FFmpegImageSharp.Services;
public class ImageProcessor
{
    public byte[] ProcessImage(FrameData frame)
    {
        using (var image = Image.Load(frame.ImageData))
        {
            // Resize the image to 240x240
            image.Mutate(x => x.Resize(240, 240));
            // Create a new 320x240 image with a custom background color
            using (var background = new Image<Bgra32>(320, 240, new Bgra32(0, 0, 0))) // Custom color: black
            {
                // Calculate the position to center the 240x240 image on the 320x240 background
                var x = (background.Width - image.Width) / 2;
                var y = (background.Height - image.Height) / 2;
                // Draw the resized image onto the background
                background.Mutate(ctx => ctx.DrawImage(image, new Point(x, y), 1f));
                background.Mutate(x => x.Rotate(90));
                using Image<Bgr24> converted2inch4Image = background.CloneAs<Bgr24>();
                var byteList = GetImageBytes(converted2inch4Image);
                return byteList;
                // Save the processed image or perform further processing
                //background.Save($"path_to_save_processed_image_{DateTime.Now.Ticks}.png");
            }
        }
    }
    public byte[] GetImageBytes(Image<Bgr24> image, int xStart = 0, int yStart = 0)
    {
        int imwidth = image.Width;
        int imheight = image.Height;
        var pix = new byte[imheight * imwidth * 2];
        for (int y = 0; y < imheight; y++)
        {
            for (int x = 0; x < imwidth; x++)
            {
                var color = image[x, y];
                pix[(y * imwidth + x) * 2] = (byte)((color.R & 0xF8) | (color.G >> 5));
                pix[(y * imwidth + x) * 2 + 1] = (byte)(((color.G << 3) & 0xE0) | (color.B >> 3));
            }
        }
        return pix;
    }
}

主程序序列化表情到json數據的代碼如下:

using System.Text.Json;
using FFmpegImageSharp.Models;
using FFmpegImageSharp.Services;
using Microsoft.Extensions.DependencyInjection;
var serviceProvider = new ServiceCollection()
            .AddSingleton<FrameExtractor>()
            .AddSingleton<StreamFrameExtractor>()
            .AddSingleton<ImageProcessor>()
            .BuildServiceProvider();
var frameExtractor = serviceProvider.GetRequiredService<FrameExtractor>();
//var streamFrameExtractor = serviceProvider.GetRequiredService<StreamFrameExtractor>();
var imageProcessor = serviceProvider.GetRequiredService<ImageProcessor>();
var videoFilePath = "anger.mp4"; // Update with your video file path
var data = new FrameMetaData
{
    Name = Path.GetFileNameWithoutExtension(videoFilePath),
    FileName = videoFilePath,
    Width = 240,
    Height = 320
};
var frames = await frameExtractor.ExtractFramesAsync(videoFilePath);
foreach (var frame in frames)
{
    var list = imageProcessor.ProcessImage(frame);
    data.FrameDatas.Add(list);
}
// JSON serialization
await File.WriteAllTextAsync($"{data.Name}.json", JsonSerializer.Serialize(data));
// JSON deserialization
var deserializedData = JsonSerializer.Deserialize<FrameMetaData>(await File.ReadAllTextAsync($"{data.Name}.json"));
// Verify deserialization
Console.WriteLine($"Name: {deserializedData?.Name}, Width: {deserializedData?.Width}, Height: {deserializedData?.Height}");
Console.WriteLine("Frame extraction and processing completed. Metadata saved to frame_metadata.json.");

通過上面的代碼就可以制作出一個表情文件了,源代碼在我另外的倉庫里,然后通過桌面機器人倉庫的代碼反序列化并且播放就好了。效果如下:

總結感悟

轉自https://www.cnblogs.com/GreenShade/p/18692804


該文章在 2025/2/6 10:26:44 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 亚洲中文无码人成网站 | 日韩欧美影视内容永久免费看 | 国产一区二区三区内射 | 免费国产美女爽到喷出水来视频 | 亚洲中文字幕久久精品无码喷水 | 999久久国产精品免费人妻 | 国产精品白丝av嫩草影院 | 东京无码熟妇人妻av在线网址 | 亚洲欧美二区三区久本道 | 丁香花依依成人社区 | 亚洲欧美日韩尤物aⅴ一区 亚洲欧美日韩在线不卡 | 欧洲av无码放荡人妇网站 | 国产精品va欧美精品 | 偷自拍亚洲视频在 | 中文字幕人妻无码系列第三区 | 欧美日韩国产精品视频 | 亚洲综合av免费在线观看 | 免费无码h肉黄动漫在线观看 | 91桃色含羞草片多多 | 最新国产精品剧情在线ss | 久久午夜羞羞影院免费观看 | 日韩一级在线播放免费观看 | 日本高清视频在线www色 | 欧美精品一区二区三区四区 | 国产成人毛片亚洲精品 | 豪妇荡乳1一5白玉兰免费下载 | 国产一精品一av一免费爽爽 | 亚洲国产精品久久久久久无码 | 国产午夜一级在线观看影院 | 国产美女亚洲精品久久久综合毛片欧美 | 国产做无码视频在线观看 | 亚洲免费无码中文在线 | 午夜激情婷婷 | 午夜福利影院私人爽 | 国产丝袜淑女在线 | 亚洲福利不卡片在 | 美女高潮无套内谢在线观看密桃 | 精品无码一区二区三区爱欲九九 | 在线免费观看毛片 | 欧美另类杂交a | 无码人妻精品一区二区在线视频 |