精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Android動(dòng)態(tài)圖片技術(shù)深度解析

移動(dòng)開發(fā) Android
Android 動(dòng)態(tài)照片技術(shù)借助精心設(shè)計(jì)的文件結(jié)構(gòu)、元數(shù)據(jù)系統(tǒng)及容器規(guī)范,實(shí)現(xiàn)了靜態(tài)與動(dòng)態(tài)內(nèi)容的無(wú)縫融合。然而,不同廠商的技術(shù)實(shí)現(xiàn)存在差異,這不可避免地導(dǎo)致了兼容性問(wèn)題 —— 各廠商的動(dòng)態(tài)照片格式往往難以互通。

1.概述

動(dòng)態(tài)照片是一種融合靜態(tài)圖片與動(dòng)態(tài)視頻的多媒體格式,其核心設(shè)計(jì)采用"主靜態(tài)文件 + 附加視頻 + 元數(shù)據(jù)"的組合模式,既能實(shí)現(xiàn)靜態(tài)展示,又可支持動(dòng)態(tài)播放,為用戶帶來(lái)更豐富的視覺體驗(yàn),同時(shí)保持了較好的兼容性。

不過(guò),由于不同手機(jī)廠商對(duì)動(dòng)態(tài)照片的技術(shù)實(shí)現(xiàn)存在差異(如封裝格式、元數(shù)據(jù)標(biāo)簽定義等),導(dǎo)致顯示和提取方式不同。目前測(cè)試了三個(gè)主流廠商,其特點(diǎn)如下:

  • 小米: 使用 Micro Video 格式,通過(guò)自定義 EXIF 字段存儲(chǔ)元數(shù)據(jù)
  • Google: 采用標(biāo)準(zhǔn) Motion Photo 格式,基于 XMP 元數(shù)據(jù)系統(tǒng)
  • OPPO: 實(shí)現(xiàn) O Live Photo 格式,支持 HDR GainMap 等高級(jí)特性

本文將深入解析 Android 動(dòng)態(tài)照片的技術(shù)原理,重點(diǎn)分析 XMP 元數(shù)據(jù)系統(tǒng),并通過(guò)小米 Micro Video、Google Motion Photo 和 OPPO O Live Photo 三個(gè)真實(shí)案例,展示不同廠商的技術(shù)實(shí)現(xiàn)細(xì)節(jié),探尋一套統(tǒng)一的檢測(cè)和處理解決方案。

2.動(dòng)態(tài)照片的核心結(jié)構(gòu)

2.1 三層架構(gòu)設(shè)計(jì)

動(dòng)態(tài)照片的基礎(chǔ)框架由三部分構(gòu)成:

主要靜態(tài)圖片文件

  • 作用:作為視覺主體,是用戶直觀看到的 "照片" 部分
  • 格式支持:JPEG、HEIC(高效圖像格式)、AVIF(新一代開放格式)
  • 內(nèi)容特點(diǎn):通常為拍攝瞬間的靜態(tài)畫面,可能包含增益圖以支持 HDR 效果

次要視頻文件

  • 作用:作為動(dòng)態(tài)補(bǔ)充,提供動(dòng)態(tài)效果
  • 內(nèi)容特點(diǎn):通常為拍攝前后 1-3 秒的短視頻片段
  • 存儲(chǔ)方式:附加在靜態(tài)文件中,用于呈現(xiàn)微小動(dòng)作、聲音等動(dòng)態(tài)元素

元數(shù)據(jù)系統(tǒng)

  • Camera XMP:定義靜態(tài)圖片與視頻的顯示規(guī)則,例如Camera:MotionPhoto屬性定義了是否是動(dòng)態(tài)照片,0 是非動(dòng)態(tài)照片;1 是動(dòng)態(tài)照片。
  • Container XMP:指引設(shè)備定位并讀取附加的視頻文件,例如Length字段定義了次要媒體內(nèi)容的字節(jié)長(zhǎng)度。

2.2 增益圖特性(可選)

動(dòng)態(tài)照片的主要靜態(tài)文件可能包含 "增益圖"(Gain Map),這一設(shè)計(jì)與 Ultra HDR JPEG 的增益圖邏輯一致:

  • 基礎(chǔ)原理:通過(guò) "基礎(chǔ)圖片 + 增益圖" 的組合實(shí)現(xiàn)高動(dòng)態(tài)范圍(HDR)效果
  • 兼容性設(shè)計(jì):支持設(shè)備渲染 HDR 效果,不支持的設(shè)備顯示基礎(chǔ)圖片

3.Android 官方動(dòng)態(tài)照片 XMP 元數(shù)據(jù)系統(tǒng)詳解

3.1 Camera XMP 元數(shù)據(jù)

命名空間 URIhttp://ns.google.com/photos/1.0/camera/默認(rèn)前綴Camera

核心屬性

屬性名

類型

說(shuō)明

Camera:MotionPhoto

Integer

0:非動(dòng)態(tài)照片;1:動(dòng)態(tài)照片;其他值視為 0

Camera:MotionPhotoVersion

Integer

動(dòng)態(tài)照片格式版本,當(dāng)前規(guī)范為"1"

Camera:MotionPhotoPresentationTimestampUs

Long

與靜態(tài)圖片對(duì)應(yīng)的視頻幀時(shí)間戳(微秒),-1 表示未設(shè)置

3.2 Container XMP 元數(shù)據(jù)

命名空間 URIhttp://ns.google.com/photos/1.0/container/默認(rèn)前綴Container

目錄結(jié)構(gòu)

Directory: 有序結(jié)構(gòu)數(shù)組
  ├── Container:Item (主圖片 - 必須是第一項(xiàng))
  ├── Container:Item (增益圖 - Ultra HDR時(shí))
  └── Container:Item (視頻文件 - 必須是最后一項(xiàng))

必需屬性

屬性名

類型

說(shuō)明

Mime

String

媒體內(nèi)容的 MIME 類型

Semantic

String

媒體內(nèi)容的語(yǔ)義含義

Length

Integer

次要媒體內(nèi)容的字節(jié)長(zhǎng)度

可選屬性

屬性名

類型

說(shuō)明

Padding

Integer

主圖片結(jié)尾到下一媒體項(xiàng)的間隔字節(jié)數(shù)

Semantic 的可能的值

語(yǔ)義值

說(shuō)明

Primary

主顯示圖片(必須有且僅有一個(gè))

MotionPhoto

視頻容器(必須有且僅有一個(gè),位于文件末尾)

GainMap

增益圖(Ultra HDR 時(shí)必需,位于視頻項(xiàng)之前)

3.3 元數(shù)據(jù)格式的解釋

命名空間的概念可以結(jié)合 Android XML 文件來(lái)理解。例如,xmlns:app="http://schemas.android.com/apk/res-auto" 定義了 app 命名空間,有了這個(gè)定義,像 MotionPhotoView 這樣的自定義視圖才能通過(guò) app:layout_constraintLeft_toLeftOf 這類帶 app 前綴的屬性來(lái)設(shè)置值。

由于不同手機(jī)廠商可能會(huì)定義同名的元數(shù)據(jù)屬性,命名空間的核心作用就是通過(guò)前綴區(qū)分這些屬性,避免因名稱重復(fù)導(dǎo)致的沖突。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/Blk_12"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <hy.sohu.com.app.ugc.share.view.widget.MotionPhotoView
        android:id="@+id/motion_photo_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

以下是一個(gè)根據(jù)官方文檔生成的 XMP 數(shù)據(jù)格式,可能不準(zhǔn)確,大概結(jié)構(gòu)是這樣的,通過(guò)下面的示例,我們可以更好的理解元數(shù)據(jù)是怎么存儲(chǔ)的。

<x:xmpmeta xmlns:x="adobe:ns:meta/">
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
        <rdf:Description rdf:about=""
            xmlns:Camera="http://ns.google.com/photos/1.0/camera/"
            xmlns:Container="http://ns.google.com/photos/1.0/container/">

            
            <Camera:MotionPhoto>1</Camera:MotionPhoto>
            <Camera:MotionPhotoVersion>1</Camera:MotionPhotoVersion>
            <Camera:MotionPhotoPresentationTimestampUs>1500000</Camera:MotionPhotoPresentationTimestampUs>

            
            <Container:Directory>
                <rdf:Seq>
                    <rdf:li rdf:parseType="Resource">
                        <Container:Item:Mime>image/jpeg</Container:Item:Mime>
                        <Container:Item:Semantic>Primary</Container:Item:Semantic>
                    </rdf:li>
                </rdf:Seq>
            </Container:Directory>
        </rdf:Description>
    </rdf:RDF>
</x:xmpmeta>

4.Android 官方動(dòng)態(tài)照片文件名模式規(guī)范

4.1 正則表達(dá)式規(guī)范

動(dòng)態(tài)照片的文件名需遵循特定正則表達(dá)式命名規(guī)則,這是官方推薦的命名規(guī)范,目的是通過(guò)文件名快速識(shí)別動(dòng)態(tài)照片。但實(shí)際情況中,各手機(jī)廠商并未統(tǒng)一遵循這一規(guī)則,導(dǎo)致該方法的識(shí)別效果大打折扣。

^([^\\s\\/\\\\][^\\/\\\\]*MP)\\.(JPG|jpg|JPEG|jpeg|HEIC|heic|AVIF|avif)

4.2 命名規(guī)則解析

前綴部分:[^\\s\\/\\\\][^\\/\\\\]*

  • 第一個(gè)字符不能是空格、斜杠或反斜杠
  • 后續(xù)字符可以是除斜杠和反斜杠之外的任意字符

標(biāo)識(shí)部分:MP

  • 作用:作為動(dòng)態(tài)照片文件的標(biāo)志性標(biāo)識(shí)
  • 位置:必須位于文件名末尾(擴(kuò)展名之前)
  • 示例IMG_1234MP.jpgDSC0056MP.heic

后綴部分:支持的擴(kuò)展名

  • JPEG.jpg.jpeg
  • HEIC.heic
  • AVIF.avif

5.Android 官方動(dòng)態(tài)照片視頻容器內(nèi)容規(guī)范

以下是 android 官方定義的動(dòng)態(tài)照片中視頻部分的編碼方式、軌道結(jié)構(gòu)、同步機(jī)制和播放行為,這些規(guī)范保證了用戶在查看動(dòng)態(tài)照片時(shí)能夠獲得流暢、一致的體驗(yàn),但不同手機(jī)廠商實(shí)現(xiàn)方式可能會(huì)不同。

5.1 軌道結(jié)構(gòu)

主視頻軌道(必需)

  • 編碼格式:AVC(H.264)、HEVC(H.265)或 AV1
  • 分辨率:無(wú)強(qiáng)制限制
  • 色彩支持

SDR:8 位,BT.709 色彩空間,sRGB 轉(zhuǎn)換

HDR:10 位,BT.2100 色彩空間,HLG/PQ 轉(zhuǎn)換

次要視頻軌道(可選)

  • 作用:高分辨率縮略圖或替代畫面
  • 編碼格式:同主視頻軌道
  • 幀率:通常較低(1-5fps)
  • 幀關(guān)聯(lián):與主軌道幀一一對(duì)應(yīng),時(shí)間戳完全相同

音頻軌道(可選)

  • 編碼格式:AAC
  • 參數(shù):16 位單聲道或立體聲
  • 采樣率:44kHz、48kHz 或 96kHz
  • 播放規(guī)則:與主視頻軌道同步

5.2 軌道排序規(guī)則

  1. 主視頻軌道:索引最小,必須是第一個(gè)視頻軌道
  2. 次要視頻軌道:索引大于主軌道,位于主軌道之后
  3. 音頻軌道:無(wú)強(qiáng)制順序,但需時(shí)間同步

6.不同動(dòng)態(tài)照片實(shí)現(xiàn)案例分析

6.1 動(dòng)態(tài)照片技術(shù)特點(diǎn)與檢測(cè)方法

Android 的ExifInterface類主要支持標(biāo)準(zhǔn) EXIF 標(biāo)簽,對(duì) XMP 命名空間的支持非常有限,例如小米的 MicroVideo 標(biāo)簽屬于自定義 XMP 命名空間,因此 ExifInterface 無(wú)法解析。實(shí)際開發(fā)中需要使用二進(jìn)制解析或 Adobe XMP SDK 進(jìn)行處理。

小米官方提供了專用 SDK,可以展示、判斷和制作動(dòng)態(tài)照片,為開發(fā)者提供了完整的解決方案。

6.2 小米 Micro Video 案例分析

小米手機(jī)使用自有的 Micro Video 格式,通過(guò)自定義 EXIF 字段存儲(chǔ)動(dòng)態(tài)照片元數(shù)據(jù):

exiftool /Users/allenzhang/Downloads/1752549853110.jpg

關(guān)鍵元數(shù)據(jù)信息:

File Name                       : 1752549853110.jpg
File Size                       : 5.5 MB
Make                            : (小米手機(jī)型號(hào))
XMP Toolkit                     : Adobe XMP Core 5.1.0-jc003
Micro Video Version             : 1
Micro Video                     : 1
Micro Video Offset              : 1735850
Micro Video Presentation Timestamp Us: 761955
Image Width                     : 3072
Image Height                    : 4096

XMP SDK 解析結(jié)果

通過(guò) Adobe XMP SDK 的 xmpMeta.dumpObject() 方法可以獲取小米動(dòng)態(tài)照片的完整 XMP 結(jié)構(gòu):

ROOT NODE
 http://ns.google.com/photos/1.0/camera/ = "GCamera:" (0x80000000 : SCHEMA_NODE)
  GCamera:MicroVideo = "1"
  GCamera:MicroVideoVersion = "1"
  GCamera:MicroVideoOffset = "1757635"
  GCamera:MicroVideoPresentationTimestampUs = "333227"
 http://ns.adobe.com/exif/1.0/ = "exif:" (0x80000000 : SCHEMA_NODE)
  exif:ImageWidth = "4096"
  exif:ImageLength = "3072"
  exif:Make = "Xiaomi"
  exif:Model = "XIAOMI Device"
 http://ns.adobe.com/xmp/note/ = "xmpNote:" (0x80000000 : SCHEMA_NODE)
  xmpNote:HasExtendedXMP = ""

技術(shù)特點(diǎn)分析

  • 小米復(fù)用了 Google 的 Camera 命名空間,并在其下擴(kuò)展了 MicroVideo 等自定義屬性(非標(biāo)準(zhǔn)屬性),這可能導(dǎo)致與其他廠商的標(biāo)準(zhǔn)實(shí)現(xiàn)產(chǎn)生沖突
  • 通過(guò) MicroVideoOffset 字段指示視頻數(shù)據(jù)位置
  • 視頻大小可能通過(guò) 文件總大小 - MicroVideoOffset 計(jì)算得出
  • 相比 Google 和 OPPO,小米的 XMP 結(jié)構(gòu)相對(duì)簡(jiǎn)單

6.3 Google Pixel 4 動(dòng)態(tài)照片案例分析

Google Pixel 手機(jī)使用標(biāo)準(zhǔn)的 Motion Photo 格式,以下是 Pixel 4 拍攝的動(dòng)態(tài)照片元數(shù)據(jù):

exiftool /Users/allenzhang/Downloads/PXL_20250722_065151464.MP.jpg

關(guān)鍵元數(shù)據(jù)信息:

File Name                       : PXL_20250722_065151464.MP.jpg
File Size                       : 4.2 MB
Make                            : Google
Camera Model Name               : Pixel 4
XMP Toolkit                     : Adobe XMP Core 5.1.0-jc003
Motion Photo                    : 1
Motion Photo Version            : 1
Motion Photo Presentation Timestamp Us: 411003
Has Extended XMP                : 4938039014578563D928899A05F0B30F
Directory Item Mime             : image/jpeg, video/mp4
Directory Item Semantic         : Primary, MotionPhoto
Directory Item Length           : 0, 870399
Directory Item Padding          : 0, 0
Motion Photo Video              : (Binary data 870399 bytes, use -b option to extract)

XMP SDK 解析結(jié)果

通過(guò) Adobe XMP SDK 的 xmpMeta.dumpObject() 方法可以獲取 Google Motion Photo 的完整 XMP 結(jié)構(gòu):

ROOT NODE
 http://ns.google.com/photos/1.0/container/ = "Container:" (0x80000000 : SCHEMA_NODE)
  Container:Directory (0x600 : ARRAY | ARRAY_ORDERED)
   [1] (0x100 : STRUCT)
    Container:Item (0x100 : STRUCT)
     Item:Length = "0"
     Item:Mime = "image/jpeg"
     Item:Padding = "0"
     Item:Semantic = "Primary"
   [2] (0x100 : STRUCT)
    Container:Item (0x100 : STRUCT)
     Item:Length = "870399"
     Item:Mime = "video/mp4"
     Item:Padding = "0"
     Item:Semantic = "MotionPhoto"
 http://ns.google.com/photos/1.0/camera/ = "GCamera:" (0x80000000 : SCHEMA_NODE)
  GCamera:MotionPhoto = "1"
  GCamera:MotionPhotoPresentationTimestampUs = "411003"
  GCamera:MotionPhotoVersion = "1"
 http://ns.adobe.com/xmp/note/ = "xmpNote:" (0x80000000 : SCHEMA_NODE)
  xmpNote:HasExtendedXMP = "4938039014578563D928899A05F0B30F"

技術(shù)特點(diǎn)分析

  • Google 使用標(biāo)準(zhǔn) Container 命名空間定義復(fù)雜的容器結(jié)構(gòu)
  • 通過(guò) Directory 數(shù)組清晰區(qū)分 Primary 圖片和 MotionPhoto 視頻
  • Item:Length 字段直接提供視頻大小信息(870,399 字節(jié))
  • HasExtendedXMP 表示使用了擴(kuò)展 XMP 存儲(chǔ)大型元數(shù)據(jù)
  • 完全符合 Adobe XMP 和 Google Motion Photo 標(biāo)準(zhǔn)

6.4 OPPO Find X8 動(dòng)態(tài)照片案例分析

OPPO 手機(jī)實(shí)現(xiàn)了自己的動(dòng)態(tài)照片格式,稱為 "O Live Photo",同時(shí)也支持標(biāo)準(zhǔn) Motion Photo:

exiftool /Users/allenzhang/Downloads/IMG20250722163505.jpg

關(guān)鍵元數(shù)據(jù)信息:

File Name                       : IMG20250722163505.jpg
File Size                       : 7.0 MB
Make                            : OPPO
Camera Model Name               : OPPO Find X8
XMP Toolkit                     : Adobe XMP Core 5.1.0-jc003
Version                         : 1.0
Motion Photo                    : 1
Motion Photo Version            : 1
Motion Photo Presentation Timestamp Us: 266704
Motion Photo Primary Presentation Timestamp Us: 266704
Motion Photo Owner              : oplus
O Live Photo Version            : 2
Video Length                    : 3334498
Directory Item Mime             : image/jpeg, image/jpeg, video/mp4
Directory Item Semantic         : Primary, GainMap, MotionPhoto
Directory Item Length           : 0, 474937, 3334834
Directory Item Padding          : 0, 0
Gain Map Image                  : (Binary data 3334834 bytes, use -b option to extract)
Motion Photo Video              : (Binary data 3334834 bytes, use -b option to extract)

XMP SDK 解析結(jié)果

通過(guò) Adobe XMP SDK 的 xmpMeta.dumpObject() 方法可以獲取 OPPO O Live Photo 的完整 XMP 結(jié)構(gòu):

ROOT NODE
 http://ns.google.com/photos/1.0/container/ = "Container:" (0x80000000 : SCHEMA_NODE)
  Container:Directory (0x600 : ARRAY | ARRAY_ORDERED)
   [1] (0x100 : STRUCT)
    Container:Item (0x100 : STRUCT)
     Item:Length = "0"
     Item:Mime = "image/jpeg"
     Item:Padding = "0"
     Item:Semantic = "Primary"
   [2] (0x100 : STRUCT)
    Container:Item (0x100 : STRUCT)
     Item:Length = "474937"
     Item:Mime = "image/jpeg"
     Item:Padding = "0"
     Item:Semantic = "GainMap"
   [3] (0x100 : STRUCT)
    Container:Item (0x100 : STRUCT)
     Item:Length = "3334834"
     Item:Mime = "video/mp4"
     Item:Semantic = "MotionPhoto"
 http://ns.google.com/photos/1.0/camera/ = "GCamera:" (0x80000000 : SCHEMA_NODE)
  GCamera:MotionPhoto = "1"
  GCamera:MotionPhotoPresentationTimestampUs = "266704"
  GCamera:MotionPhotoVersion = "1"
 http://ns.oplus.com/photos/1.0/camera/ = "OpCamera:" (0x80000000 : SCHEMA_NODE)
  OpCamera:MotionPhotoOwner = "oplus"
  OpCamera:MotionPhotoPrimaryPresentationTimestampUs = "266704"
  OpCamera:OLivePhotoVersion = "2"
  OpCamera:VideoLength = "3334498"
 http://ns.adobe.com/hdr-gain-map/1.0/ = "hdrgm:" (0x80000000 : SCHEMA_NODE)
  hdrgm:Version = "1.0"

技術(shù)特點(diǎn)分析

  • OPPO 中的這個(gè)例子包含三層容器結(jié)構(gòu):Primary + GainMap + MotionPhoto
  • 使用專有命名空間 http://ns.oplus.com/photos/1.0/camera/ 存儲(chǔ) O Live Photo 擴(kuò)展信息
  • 支持 HDR 增益圖(GainMap),文件大小 474,937 字節(jié)
  • 兩個(gè)視頻大小字段:Container 中的 3,334,834 字節(jié)和 OPPO 專有的 3,334,498 字節(jié)(OPPO 視頻項(xiàng)未顯式定義 Padding 字段,推測(cè)其 Length 包含的額外字節(jié)(336 字節(jié))為隱式 Padding,可能與廠商編碼邏輯有關(guān))
  • 集成 Adobe HDR-Gain-Map 標(biāo)準(zhǔn),版本 1.0
  • 雙時(shí)間戳機(jī)制:標(biāo)準(zhǔn)時(shí)間戳和 Primary 專用時(shí)間戳
  • 向后兼容 Google Motion Photo 標(biāo)準(zhǔn)的同時(shí)提供 OPPO 增強(qiáng)功能

6.5 三大廠商動(dòng)態(tài)照片格式對(duì)比分析

特征項(xiàng)

小米 Micro Video

Google Motion Photo

OPPO O Live Photo

格式標(biāo)識(shí)

Micro Video: 1

Motion Photo: 1

Motion Photo: 1

 + O Live Photo Version: 2

存儲(chǔ)位置

EXIF 自定義字段

標(biāo)準(zhǔn) XMP 元數(shù)據(jù)

標(biāo)準(zhǔn) XMP 元數(shù)據(jù) + OPPO 擴(kuò)展

視頻標(biāo)識(shí)

Micro Video Offset

Directory Item Length

Video Length

 + Directory Item Length

時(shí)間戳字段

Micro Video Presentation Timestamp Us

Motion Photo Presentation Timestamp Us

雙時(shí)間戳支持

容器結(jié)構(gòu)

簡(jiǎn)單二進(jìn)制附加

符合 Google 容器規(guī)范

支持 GainMap (HDR)

文件大小占比

視頻約 70%

視頻約 20%

視頻約 47%

兼容性

小米專有

標(biāo)準(zhǔn)格式

OPPO 擴(kuò)展 + 標(biāo)準(zhǔn)兼容

特性

不支持增益圖、HDR

支持增益圖、HDR

支持增益圖、HDR

音頻支持

不支持

部分支持

完全支持

XMP 命名空間

com.mi

com.google

com.oppo

詳細(xì)分析:

1)小米 Micro Video(傳統(tǒng)格式)

文件總大小: 5.5 MB
├── 靜態(tài)圖片: 1.74 MB (約 31%)
└── 視頻數(shù)據(jù): 4.03 MB (約 69%)
  • 使用自定義 EXIF 字段存儲(chǔ)元數(shù)據(jù)
  • 視頻直接附加在靜態(tài)圖片后
  • 格式簡(jiǎn)單但兼容性有限

2)Google Motion Photo(標(biāo)準(zhǔn)格式)

文件總大小: 4.2 MB
├── 靜態(tài)圖片: 約 3.3 MB (約 79%)
├── 視頻數(shù)據(jù): 870 KB (約 20%)
└── Extended XMP: 少量元數(shù)據(jù)
  • 完全符合 Google Motion Photo 標(biāo)準(zhǔn)
  • 使用 Extended XMP 處理大型元數(shù)據(jù)
  • 容器結(jié)構(gòu)清晰,支持多種媒體類型

3)OPPO O Live Photo(混合格式)

文件總大小: 7.0 MB
├── 靜態(tài)圖片: 約 3.2 MB (約 46%)
├── GainMap (HDR): 475 KB (約 7%)
└── 視頻數(shù)據(jù): 3.3 MB (約 47%)
  • 支持 HDR 增益圖 (GainMap)
  • 雙時(shí)間戳機(jī)制提供更精確的同步
  • 向后兼容標(biāo)準(zhǔn) Motion Photo 格式

7.處理動(dòng)態(tài)照片的 Kotlin 實(shí)現(xiàn)

7.1 依賴配置

首先在 build.gradle 中添加必要的依賴:

dependencies {
    // Adobe XMP SDK
    implementation 'com.adobe.xmp:xmpcore:6.1.11'

    // 協(xié)程支持
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'

    // 文件處理
    implementation 'androidx.core:core-ktx:1.12.0'
}

7.2 動(dòng)態(tài)照片檢測(cè)方法和解析方法

基于以上對(duì)三大廠商動(dòng)態(tài)照片格式的分析,我嘗試設(shè)計(jì)了一套統(tǒng)一的動(dòng)態(tài)照片檢測(cè)解決方案。該方案采用雙重檢測(cè)策略:首先通過(guò) extractXMPFromJPEG 方法解析 XMP 元數(shù)據(jù)來(lái)識(shí)別動(dòng)態(tài)照片類型和視頻信息;當(dāng) XMP 元數(shù)據(jù)不完整或缺失時(shí),則使用 findMp4HeaderOffset 方法直接從文件二進(jìn)制數(shù)據(jù)中定位并提取視頻信息。這種可以提供更好的兼容性和可靠性。

下面以 OPPO 動(dòng)態(tài)照片為例,展示完整的元數(shù)據(jù)解析和視頻提取實(shí)現(xiàn)。小米和 Google 動(dòng)態(tài)照片的處理邏輯與此類似,遵循相同的接口設(shè)計(jì)模式。

class UnifiedMotionPhotoExtractor {
    companionobject {
        constval TAG = "MotionPhotoExtractor"
        privateconstval BUFFER_SIZE = 8192

        // MP4文件頭標(biāo)識(shí)
        privateval FTYP_SIGNATURE = byteArrayOf('f'.toByte(), 't'.toByte(), 'y'.toByte(), 'p'.toByte())
        privateval FTYPMP4_SIGNATURE = byteArrayOf('f'.toByte(), 't'.toByte(), 'y'.toByte(), 'p'.toByte(),
                                               'm'.toByte(), 'p'.toByte(), '4'.toByte())
        privateval FTYPMP42_SIGNATURE = byteArrayOf('f'.toByte(), 't'.toByte(), 'y'.toByte(), 'p'.toByte(),
                                                'm'.toByte(), 'p'.toByte(), '4'.toByte(), '2'.toByte())
        privateval FTYPISOM_SIGNATURE = byteArrayOf('f'.toByte(), 't'.toByte(), 'y'.toByte(), 'p'.toByte(),
                                                'i'.toByte(), 's'.toByte(), 'o'.toByte(), 'm'.toByte())

        // 元數(shù)據(jù)標(biāo)簽
        privateconstval XIAOMI_MICRO_VIDEO = "MicroVideo"
        privateconstval XIAOMI_MICRO_VIDEO_OFFSET = "MicroVideoOffset"
        privateconstval GCAMERA_MICRO_VIDEO = "GCamera:MicroVideo"
        privateconstval GCAMERA_MICRO_VIDEO_OFFSET = "GCamera:MicroVideoOffset"
        privateconstval GOOGLE_MOTION_PHOTO = "GCamera:MotionPhoto"
        privateconstval OPPO_LIVE_PHOTO = "OpCamera:OLivePhotoVersion"

        // 進(jìn)度回調(diào)間隔(字節(jié)數(shù))
        privateconstval PROGRESS_INTERVAL = 100000

        // JPEG 段標(biāo)記
        privateconstval JPEG_SOI = 0xFFD8
        privateconstval JPEG_APP1 = 0xFFE1
        privateconstval JPEG_SOS = 0xFFDA

        // XMP 頭標(biāo)識(shí)常量
        privateval XMP_HEADER = "http://ns.adobe.com/xap/1.0/".toByteArray()
    }


    /**
     * 從 JPEG 文件中提取 XMP 元數(shù)據(jù)
     */
    privatefun extractXMPFromJPEG(filePath: String): XMPMeta? {
        returntry {
            RandomAccessFile(filePath, "r").use { raf ->
                // 驗(yàn)證 JPEG 文件頭
                if (raf.readUnsignedShort() != JPEG_SOI) {
                    returnnull
                }

                while (true) {
                    // 讀取段標(biāo)記
                    val marker = raf.readUnsignedShort()

                    // 如果到達(dá)圖像數(shù)據(jù)段,停止解析
                    if (marker == JPEG_SOS) {
                        break
                    }

                    // 只處理 APP1 段
                    if (marker == JPEG_APP1) {
                        val segmentLength = raf.readUnsignedShort()
                        val segmentData = ByteArray(segmentLength - 2)
                        raf.readFully(segmentData)

                        // 檢查是否為 XMP 數(shù)據(jù)
                        if (isXMPSegment(segmentData)) {
                            Log.d("chao"," 發(fā)現(xiàn) XMP 數(shù)據(jù)段,文件: ${filePath}")
                            return parseXMPFromSegment(segmentData)
                        }
                    } else {
                        // 跳過(guò)其他段
                        val segmentLength = raf.readUnsignedShort()
                        raf.skipBytes(segmentLength - 2)
                    }
                }
            }
            null
        } catch (e: Exception) {
            Log.e(TAG, "XMP 提取失敗: ${e.message}", e)
            null
        }
    }

    /**
     * 檢查是否為 XMP 段
     */
    privatefun isXMPSegment(data: ByteArray): Boolean {
        if (data.size < XMP_HEADER.size) returnfalse
        for (i in XMP_HEADER.indices) {
            if (data[i] != XMP_HEADER[i]) returnfalse
        }
        returntrue
    }

    /**
     * 從段數(shù)據(jù)中解析 XMP
     */
    privatefun parseXMPFromSegment(segmentData: ByteArray): XMPMeta? {
        returntry {
            val xmpDataStart = XMP_HEADER.size + 1// +1 for null terminator
            val xmpData = segmentData.copyOfRange(xmpDataStart, segmentData.size)
            XMPMetaFactory.parseFromBuffer(xmpData)
        } catch (e: XMPException) {
            Log.e(TAG, "XMP 解析失敗: ${e.message}", e)
            null
        }
    }


    // 這里是oppo手機(jī)提取xmp方法,其它手機(jī)的類似
    privatefun checkOppoLivePhotoInXMP(xmpMeta: XMPMeta): MotionPhotoResult? {
        returntry {
            val cameraNamespace = "http://ns.google.com/photos/1.0/camera/"
            val oppoNamespace = "http://ns.oplus.com/photos/1.0/camera/"http:// OPPO專有命名空間
            val containerNamespace = "http://ns.google.com/photos/1.0/container/"
            val hdrNamespace = "http://ns.adobe.com/hdr-gain-map/1.0/"

            // 首先檢查OPPO專有命名空間
            val oppoMotionPhotoOwner = try {
                xmpMeta.getPropertyString(oppoNamespace, "MotionPhotoOwner")
            } catch (e: Exception) { null }

            val oppoOLivePhotoVersion = try {
                xmpMeta.getPropertyString(oppoNamespace, "OLivePhotoVersion")
            } catch (e: Exception) { null }

            val oppoVideoLength = try {
                xmpMeta.getPropertyString(oppoNamespace, "VideoLength")?.toLongOrNull()
            } catch (e: Exception) { null }

            // 檢查標(biāo)準(zhǔn)命名空間中的Motion Photo標(biāo)識(shí)
            val motionPhoto = try {
                xmpMeta.getPropertyInteger(cameraNamespace, "MotionPhoto")
            } catch (e: Exception) { null }

            // 如果是OPPO格式 (有OPPO專有標(biāo)識(shí)或VideoLength字段)
            val isOppoFormat = oppoMotionPhotoOwner == "oplus" ||
                    oppoOLivePhotoVersion != null ||
                    oppoVideoLength != null

            if (motionPhoto == 1 && isOppoFormat) {
                Log.d("XMP", "檢測(cè)到OPPO O Live Photo格式")

                val version = try {
                    // 優(yōu)先使用OPPO版本,回退到標(biāo)準(zhǔn)版本
                    oppoOLivePhotoVersion?.toIntOrNull()
                        ?: xmpMeta.getPropertyInteger(cameraNamespace, "MotionPhotoVersion")
                } catch (e: Exception) { 1 }

                val timestamp = try {
                    // 優(yōu)先使用OPPO的Primary時(shí)間戳
                    xmpMeta.getPropertyLong(oppoNamespace, "MotionPhotoPrimaryPresentationTimestampUs")
                } catch (e: Exception) {
                    try {
                        // 回退到標(biāo)準(zhǔn)時(shí)間戳
                        xmpMeta.getPropertyLong(cameraNamespace, "MotionPhotoPresentationTimestampUs")
                    } catch (e2: Exception) { -1L }
                }

                // 解析Container結(jié)構(gòu)獲取視頻大小和GainMap信息
                val containerInfo = parseOppoContainerInfo(xmpMeta, containerNamespace)

                // 優(yōu)先使用Container中的視頻大小,回退到OPPO的VideoLength字段
                val videoSize = containerInfo.videoSize.takeIf { it > 0 }
                    ?: oppoVideoLength ?: -1L

                // 檢查是否有HDR GainMap
                val hasHdrGainMap = try {
                    val hdrVersion = xmpMeta.getPropertyString(hdrNamespace, "Version")
                    hdrVersion != null && containerInfo.hasGainMap
                } catch (e: Exception) {
                    containerInfo.hasGainMap
                }

                Log.d("XMP", "OPPO檢測(cè)結(jié)果 - VideoSize: $videoSize, HasGainMap: $hasHdrGainMap, Version: $version")

                return MotionPhotoResult(
                    type = MotionPhotoType.OPPO_LIVE_PHOTO,
                    vendor = "OPPO",
                    version = version,
                    videoSize = videoSize,
                    presentationTimestamp = timestamp,
                    detectionMethod = "XMP SDK",
                    hasGainMap = hasHdrGainMap
                )
            }

            null
        } catch (e: Exception) {
            Log.e("XMP", "OPPO檢測(cè)失敗: ${e.message}", e)
            null
        }
    }



    /**
     * 提取OPPO動(dòng)態(tài)照片中的視頻
     */
    privatefun extractVideo(
        inputFile: File,
        outputFile: File,
        videoSize: Long = 0L,
        videoOffset: Long = 0L,
        progressCallback: ((Long, Long) -> Unit)?
    ): Boolean {
        // OPPO動(dòng)態(tài)照片可能包含增益圖,需要特殊處理
        // 這里使用簡(jiǎn)化方法,直接搜索MP4文件頭
        val offset = findMp4HeaderOffset(inputFile.path)?:0

        Log.d(TAG,"找到視頻數(shù)據(jù),偏移量: $offset")

        // 嘗試多個(gè)可能的偏移量
        val possibleOffsets = listOf(
            videoOffset,
            offset
        )

        for (tryOffset in possibleOffsets) {
            if (tryOffset < 0) continue

            val tempFile = File.createTempFile("motion_video_", ".mp4")
            extractVideoData(inputFile, tempFile, tryOffset, progressCallback)

            if (isValidMp4(tempFile)) {
                Log.d(TAG,"? 偏移量 $tryOffset 提取的文件是有效的MP4格式")
                tempFile.copyTo(outputFile, overwrite = true)
                tempFile.delete()
                returntrue
            }

            tempFile.delete()
        }

        return isValidMp4(outputFile)
    }

    /**
     * 查找MP4文件頭的偏移量
     */
    privatefun findMp4HeaderOffset(filePath: String): Long? {
        val file = File(filePath)
        if (!file.exists() || file.length() < 8) {
            returnnull
        }

        // 只搜索文件的后半部分,因?yàn)橐曨l通常在文件末尾
        val fileSize = file.length()
        val startOffset = maxOf(0, fileSize / 2)

        RandomAccessFile(filePath, "r").use { raf ->
            raf.seek(startOffset)

            val buffer = ByteArray(BUFFER_SIZE)
            val window = ByteArray(8) // 滑動(dòng)窗口,用于查找簽名
            var bytesRead: Int
            var currentOffset = startOffset

            while (raf.read(buffer).also { bytesRead = it } > 0) {
                for (i in0 until bytesRead) {
                    // 更新滑動(dòng)窗口
                    System.arraycopy(window, 1, window, 0, window.size - 1)
                    window[window.size - 1] = buffer[i]

                    // 檢查當(dāng)前窗口是否包含任何一種MP4文件頭標(biāo)識(shí)
                    val signatureOffset = containsSignature(window)
                    if (signatureOffset != -1) {
                        // 找到了MP4文件頭,返回文件中的實(shí)際偏移量
                        // 減去4是因?yàn)镸P4的box大小字段在標(biāo)識(shí)符之前
                        return currentOffset + i - (window.size - signatureOffset) + 1 - 4
                    }
                }
                currentOffset += bytesRead
            }
        }

        returnnull
    }

    /**
     * 檢查給定的字節(jié)數(shù)組是否包含任何一種MP4文件頭標(biāo)識(shí)
     *
     * @return 如果包含,返回標(biāo)識(shí)符在數(shù)組中的起始位置;否則返回-1
     */
    privatefun containsSignature(data: ByteArray): Int {
        // 按優(yōu)先級(jí)順序檢查各種MP4文件頭標(biāo)識(shí)
        val signatures = listOf(FTYPMP42_SIGNATURE, FTYPMP4_SIGNATURE, FTYPISOM_SIGNATURE, FTYP_SIGNATURE)

        for (signature in signatures) {
            for (i in0..data.size - signature.size) {
                var found = true
                for (j in signature.indices) {
                    if (data[i + j] != signature[j]) {
                        found = false
                        break
                    }
                }
                if (found) return i
            }
        }

        return -1
    }

    /**
     * 從輸入文件的指定偏移量開始提取視頻數(shù)據(jù)到輸出文件
     */
    privatefun extractVideoData(
        inputFile: File,
        outputFile: File,
        offset: Long,
        progressCallback: ((Long, Long) -> Unit)?
    ) {
        val fileSize = inputFile.length()
        val videoSize = fileSize - offset

        FileInputStream(inputFile).use { input ->
            FileOutputStream(outputFile).use { output ->
                // 跳過(guò)偏移量之前的數(shù)據(jù)
                input.skip(offset)

                val buffer = ByteArray(BUFFER_SIZE)
                var bytesRead: Int
                var totalBytesRead: Long = 0
                var lastProgressUpdate: Long = 0

                while (input.read(buffer).also { bytesRead = it } > 0) {
                    output.write(buffer, 0, bytesRead)

                    totalBytesRead += bytesRead

                    // 更新進(jìn)度
                    if (progressCallback != null && totalBytesRead - lastProgressUpdate > PROGRESS_INTERVAL) {
                        progressCallback(totalBytesRead, videoSize)
                        lastProgressUpdate = totalBytesRead
                    }
                }

                // 最終進(jìn)度更新
                progressCallback?.invoke(totalBytesRead, videoSize)
            }
        }
    }

    /**
     * 檢查文件是否為有效的MP4格式
     */
    privatefun isValidMp4(file: File): Boolean {
        if (!file.exists() || file.length() < 8) returnfalse

        FileInputStream(file).use { input ->
            val header = ByteArray(8)
            if (input.read(header) != header.size) returnfalse

            // 檢查文件頭是否包含ftyp標(biāo)識(shí)
            val headerStr = String(header, StandardCharsets.UTF_8)
            return headerStr.contains("ftyp")
        }
    }
}

7.3 動(dòng)態(tài)照片播放組件

為了更好地展示動(dòng)態(tài)照片,我們可以創(chuàng)建一個(gè)專用的 MotionPhotoView 組件,ImageView用來(lái)顯示靜態(tài)圖片,VideoView用來(lái)顯示視頻,主要方法如下:

/**
 * 動(dòng)態(tài)照片播放組件
 * 支持靜態(tài)圖片顯示和視頻播放切換
 */
class MotionPhotoView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    privateval imageView: ImageView
    privateval videoView: VideoView
    privatevar motionPhotoResult: MotionPhotoResult? = null

    /**
     * 設(shè)置動(dòng)態(tài)照片數(shù)據(jù)
     */
    fun setMotionPhoto(imagePath: String, result: MotionPhotoResult) {
        this.originalImagePath = imagePath
        this.motionPhotoResult = result
        // 顯示靜態(tài)圖片
        loadStaticImage(imagePath)
    }

    /**
     * 播放視頻
     */
    fun playVideo() {
        try {
            Log.d("MotionPhotoView", "開始播放視頻: $videoPath")
            videoView.setVideoPath(videoPath)
            videoView.isVisible = true
            imageView.isVisible = false
            videoView.start()
            isVideoPlaying = true

        } catch (e: Exception) {
            Log.e("MotionPhotoView", "播放視頻異常: ${e.message}", e)
            onError?.invoke("視頻播放失敗: ${e.message}")
            showStaticImage()
        }
    }

    /**
     * 停止視頻播放
     */
    fun stopVideo() {
        if (videoView.isPlaying) {
            videoView.stopPlayback()
        }
        showStaticImage()
    }

    /**
     * 暫停視頻
     */
    fun pauseVideo() {
        if (videoView.isPlaying) {
            videoView.pause()
        }
    }

    /**
     * 設(shè)置VideoView回調(diào)
     */
    privatefun setupVideoCallbacks() {
        videoView.setOnPreparedListener { mediaPlayer ->
            Log.d("MotionPhotoView", "視頻準(zhǔn)備完成")

            // 設(shè)置循環(huán)播放
            if (autoLoop) {
                mediaPlayer.isLooping = true
            }
            // 根據(jù)時(shí)間戳定位播放位置
            motionPhotoResult?.presentationTimestamp?.let { timestamp ->
                if (timestamp > 0) {
                    val seekPosition = (timestamp / 1000).toInt() // 轉(zhuǎn)換為毫秒
                    Log.d("MotionPhotoView", "定位到時(shí)間戳: ${seekPosition}ms (原始: ${timestamp}μs)")
                    mediaPlayer.seekTo(seekPosition)
                }
            }
        }

        videoView.setOnCompletionListener {
            Log.d("MotionPhotoView", "視頻播放完成")
            if (!autoLoop) {
                showStaticImage()
            }
        }

        videoView.setOnErrorListener { _, what, extra ->
            showStaticImage()
            true
        }
    }
}

08總結(jié)與展望

Android 動(dòng)態(tài)照片技術(shù)借助精心設(shè)計(jì)的文件結(jié)構(gòu)、元數(shù)據(jù)系統(tǒng)及容器規(guī)范,實(shí)現(xiàn)了靜態(tài)與動(dòng)態(tài)內(nèi)容的無(wú)縫融合。然而,不同廠商的技術(shù)實(shí)現(xiàn)存在差異,這不可避免地導(dǎo)致了兼容性問(wèn)題 —— 各廠商的動(dòng)態(tài)照片格式往往難以互通。

本文通過(guò)深入解析 XMP 元數(shù)據(jù)系統(tǒng),并結(jié)合小米 Micro Video、Google Motion Photo 和 OPPO O Live Photo 的真實(shí)案例,詳細(xì)展示了動(dòng)態(tài)照片的實(shí)現(xiàn)原理。對(duì)于開發(fā)者而言,理解這些技術(shù)細(xì)節(jié)不僅有助于構(gòu)建更好的應(yīng)用體驗(yàn),也為跨平臺(tái)兼容性處理提供了重要參考。

責(zé)任編輯:武曉燕 來(lái)源: 搜狐技術(shù)產(chǎn)品
相關(guān)推薦

2009-02-05 17:09:02

動(dòng)態(tài)圖片JSPTomcat

2012-05-24 15:41:38

PHP

2021-05-13 15:23:31

人工智能深度學(xué)習(xí)

2012-11-20 10:23:47

云計(jì)算效用計(jì)算網(wǎng)格計(jì)算

2009-12-08 11:16:07

PHP動(dòng)態(tài)圖像創(chuàng)建

2021-10-12 11:07:33

動(dòng)畫深度Android

2011-06-02 11:13:10

Android Activity

2011-06-21 18:02:14

Qt 動(dòng)態(tài) 鏈接庫(kù)

2024-09-29 08:00:00

動(dòng)態(tài)代理RPC架構(gòu)微服務(wù)架構(gòu)

2023-06-09 15:34:32

數(shù)字孿生物聯(lián)網(wǎng)

2025-09-15 06:25:00

2021-04-18 20:49:03

Pyecharts圖表 組件

2009-08-11 13:27:09

C#動(dòng)態(tài)圖像按鈕

2009-05-13 09:10:59

Facebook存儲(chǔ)基礎(chǔ)架構(gòu)照片應(yīng)用程序

2011-05-27 17:28:01

Android

2024-09-19 08:08:25

2012-05-23 11:17:58

2024-09-19 08:49:13

2023-06-13 09:53:59

智能汽車

2011-05-05 14:28:47

散熱投影機(jī)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

成人av福利| 国产成人无码精品久久久久| 久久99国产精品二区高清软件| 国产日产欧美一区| 国产日本欧美一区二区三区在线 | 男插女免费视频| 成人毛片视频免费看| 久久精品官网| 久久手机免费视频| 人妻换人妻a片爽麻豆| 97se综合| 一区二区三区中文字幕精品精品| 久久国产精品一区二区三区四区| 中文字幕777| 亚洲天堂男人| 中文字幕久久久| 麻豆tv在线观看| 欧美韩国亚洲| 污片在线观看一区二区| 亚洲一区高清| 欧美日韩国产中文字幕在线| 国内外成人在线视频| 91精品国产91久久| 午夜免费激情视频| 精品久久久久久久久久久下田 | 欧美成年人视频在线观看| caoprom在线| 国产精品电影一区二区三区| 国产精品久久久久久免费观看| 中文文字幕一区二区三三| 伊人久久亚洲影院| 超在线视频97| 婷婷色一区二区三区| 精品精品国产三级a∨在线| 欧美一区二区私人影院日本| 五月天激情视频在线观看| 国产99在线观看| 亚洲一区精品在线| 国产911在线观看| 日本成人网址| 国产精品入口麻豆九色| 欧美精品一区二区视频| 日本人妻丰满熟妇久久久久久| 日韩成人动漫| 18深夜在线观看免费视频| 一区二区三区四区精品| 久久99久久99精品免视看婷婷| 成人免费图片免费观看| 网站永久看片免费| 国产女人18毛片| 亚洲第五色综合网| 国产日韩二区| 国产偷拍一区二区| 蜜桃视频一区二区| 国产精品av免费在线观看| 日韩黄色三级视频| 黄色综合网站| 欧美精品www| 免费中文字幕在线观看| 欧美1区免费| 久热精品视频在线观看| 国产精品白丝喷水在线观看| 欧美aaaa视频| 久久精品久久久久久国产 免费| 欧洲美熟女乱又伦| 日本大胆欧美| 久久精品国产久精国产思思| 91免费公开视频| 欧美va天堂| 久久久免费观看视频| 日韩人妻无码一区二区三区99| 亚洲精品人人| 欧美最猛性xxxx| 超碰在线97观看| 久久99久久99| 国产精品福利视频| 亚洲欧美丝袜中文综合| 国产午夜精品美女毛片视频| 欧美一级爱爱| 免费在线观看黄| 有坂深雪av一区二区精品| 人人妻人人做人人爽| 九色porny自拍视频在线观看| 色综合婷婷久久| 中文字幕成人在线视频| 亚洲经典视频| 亚洲久久久久久久久久久| 亚洲无人区码一码二码三码的含义| 日韩精品一区二区三区免费观看| 欧美成在线视频| 狠狠躁夜夜躁人人爽天天高潮| 午夜在线视频观看日韩17c| 国产精品欧美日韩| 午夜精品一二三区| 国产色婷婷亚洲99精品小说| 97超碰人人爱| 另类专区亚洲| 欧美成人性战久久| 国产高潮呻吟久久| 欧美日韩18| 国产成人在线精品| 性猛交xxxx乱大交孕妇印度| 久久精品免费在线观看| 黄色片免费在线观看视频| 久久久久久久| 精品福利一区二区三区免费视频| 日韩人妻无码精品综合区| 国产精品videossex久久发布| 欧洲中文字幕国产精品| 国产成人三级一区二区在线观看一 | 国产免费一区二区视频| 成人午夜亚洲| 日韩国产精品一区| 四虎影院中文字幕| 亚洲欧美日本视频在线观看| 亚洲一区二区免费在线| av在线三区| 欧美日韩激情美女| 欧美69精品久久久久久不卡| 成人羞羞网站入口免费| 欧美激情videos| 国产伦精品一区二区三区四区| 久久久国产综合精品女国产盗摄| 国产www免费| 清纯唯美激情亚洲| 色诱女教师一区二区三区| 日韩精品1区2区| 懂色av一区二区三区免费观看| 亚洲欧洲久久| 向日葵视频成人app网址| 亚洲国产成人av在线| 欧美日韩成人免费观看| 精品一区二区三区在线播放| 亚洲国产一区二区三区在线播| 一区二区电影免费观看| 亚洲激情视频在线播放| 久久亚洲成人av| 国产伦理精品不卡| 99re99热| 亚洲男人在线| 日韩视频亚洲视频| 中文字幕在线网址| 国产日韩精品一区| 无遮挡又爽又刺激的视频| 日韩成人动漫在线观看| 久久久噜噜噜久噜久久| 日韩中文字幕影院| 亚洲国产精品一区二区久久恐怖片 | 四虎永久精品在线| 日韩电影av| 欧美日韩一二三四五区| 国产精品手机在线观看| 欧美精品一卡| 91在线视频九色| 黄色片免费在线观看| 欧美日韩一区二区在线观看| 欧美性受xxxx黑人| 蜜臀99久久精品久久久久久软件| 日韩欧美精品一区二区| 69堂精品视频在线播放| 中文字幕久热精品在线视频 | 色综合色综合色综合色综合色综合| 白嫩情侣偷拍呻吟刺激| 日韩亚洲国产欧美| 麻豆成人小视频| 激情亚洲影院在线观看| 中文字幕在线观看亚洲| 中文字幕在线观看欧美| 亚洲天堂免费看| www日本在线观看| 亚洲日本激情| 日韩精品久久一区| 日本免费成人| 久久99热精品| 亚洲欧美日韩综合在线| 欧美性猛交xxxx黑人交| 日本一级特级毛片视频| 成人一区二区三区视频 | 日韩激情在线观看| 日韩一区有码在线| 宅男66日本亚洲欧美视频| 黄色污污网站在线观看| 中文字幕免费一区| 国产精品二区视频| 亚洲看片免费| 亚洲国产成人不卡| 日韩中文字幕在线一区| 欧美亚洲激情在线| 永久免费av在线| 日韩你懂的在线播放| 99精品在线播放| 亚洲手机成人高清视频| 色噜噜在线观看| 激情五月播播久久久精品| 亚洲国产精品无码av| 欧美一区2区| 懂色中文一区二区三区在线视频| 高清不卡亚洲| 欧美另类暴力丝袜| 国产视频二区在线观看| 日韩精品一区二区三区视频| 无码人妻精品一区二区50| 亚洲精品午夜久久久| av女人的天堂| 99视频精品免费视频| 不卡的在线视频| 亚洲中午字幕| 99久热在线精品视频| 成人精品电影| 久久精品综合一区| 精品久久免费| 国产精品入口福利| 免费v片在线观看| 久久这里有精品| 福利片在线看| 亚洲精品久久久一区二区三区 | 91精品国产入口在线| 亚洲欧美另类在线视频| 亚洲高清不卡在线观看| 神马久久精品综合| 国产色综合久久| 一区二区视频观看| 成人午夜伦理影院| 久久久福利影院| 蜜桃视频一区二区三区在线观看| 国产性xxxx18免费观看视频| 尤物网精品视频| 日韩欧美视频免费在线观看| 99久久激情| 亚洲三区在线| 欧美日韩中文字幕一区二区三区| 久久精品magnetxturnbtih| 99re91这里只有精品| 亚洲自拍高清视频网站| www一区二区三区| 国产日韩中文字幕| 精品福利在线| 国产欧美最新羞羞视频在线观看| 日韩精品一区二区三区| 日本午夜人人精品| 台湾佬成人网| 国产精品久久国产精品99gif| 在线人成日本视频| 欧美一级片免费在线| 人人草在线视频| 4438全国亚洲精品在线观看视频| caoporn视频在线| 久久久亚洲成人| 精品一性一色一乱农村| 欧美激情国产日韩精品一区18| 国产人成网在线播放va免费| 超碰91人人草人人干| 伊人电影在线观看| 欧美激情一区二区三区成人| 黄视频在线免费看| 午夜精品免费视频| 岛国av免费在线观看| 亚州精品天堂中文字幕| 久久影院午夜精品| 日本中文字幕久久看| 视频在线日韩| 国产日韩专区在线| 一区二区三区四区视频免费观看| 成人高清在线观看| 欧美黑人巨大videos精品| 久久久久久国产精品mv| 国产在视频线精品视频www666| 欧美最大成人综合网| 欧美激情国产在线| 精品免费久久久久久久| 99精品国产99久久久久久福利| 久久9精品区-无套内射无码| 日本最新不卡在线| 91精产国品一二三产区别沈先生| 国产精品12区| 国产老熟女伦老熟妇露脸| 久久久久久久久久久99999| 午夜黄色福利视频| 亚洲一区影音先锋| 免费看污视频的网站| 欧美精品日日鲁夜夜添| 风流老熟女一区二区三区| 日韩精品亚洲元码| 巨大荫蒂视频欧美大片| 久久免费精品日本久久中文字幕| 午夜精品成人av| 91最新国产视频| 日本中文字幕在线一区| 中文字幕日韩精品久久| 日韩天堂av| 天天视频天天爽| 成人av电影在线播放| 国产真人真事毛片视频| 一卡二卡三卡日韩欧美| 午夜久久久久久久久久影院| 欧美一区二区三区思思人| 欧洲毛片在线| 欧美日本精品在线| 日韩漫画puputoon| 国产超碰91| 欧美3p视频| 国产av无码专区亚洲精品| 国产精品18久久久久久vr| 免费一级黄色录像| 亚洲 欧美综合在线网络| 91tv国产成人福利| 日韩精品欧美国产精品忘忧草| 国产三级在线播放| 国产精品爱久久久久久久| 风间由美性色一区二区三区四区| 视频一区视频二区视频三区高| 欧美激情五月| 色天使在线观看| 久久久欧美精品sm网站| 国产极品美女高潮无套嗷嗷叫酒店| 欧美日韩三级在线| 日本在线一二三| 欧美极品欧美精品欧美视频| 婷婷久久免费视频| 日韩精品大片| 亚洲综合不卡| 大尺度在线观看| 一区二区三区在线免费播放| 这里只有精品9| 在线观看日韩专区| 一区二区乱码| 精品国产福利| 99pao成人国产永久免费视频| 视频区 图片区 小说区| 亚洲欧洲精品成人久久奇米网| 国产91精品看黄网站在线观看| 亚洲国产精品va| 久草成色在线| 成人av资源网| 国语精品一区| 国产xxx在线观看 | 日本系列第一页| 欧美xxxx在线观看| 在线播放蜜桃麻豆| 亚洲最大福利网| 欧美激情第二页| 日本人妻一区二区三区| 亚洲综合色在线| 男人天堂网在线视频| 欧美精品xxx| 国产伦精品一区二区三区在线播放| 亚洲熟妇无码av在线播放| 国产经典欧美精品| 久久这里只有精品免费| 欧美精品一区二区三区蜜桃视频 | 欧美日韩在线影院| 全部免费毛片在线播放网站| 91av在线精品| 国产免费av一区二区三区| 能在线观看的av网站| 欧美激情一区二区在线| 一区不卡在线观看| www.亚洲免费视频| 日本少妇精品亚洲第一区| 国产精品8888| 99久久精品免费看国产| 亚洲欧美精品一区二区三区| 亚洲欧美在线一区二区| 日本成人福利| 欧美日韩一级在线 | 一区二区不卡免费视频| 91久久国产最好的精华液| jizz在线观看| 91在线国产电影| 极品日韩av| www在线观看免费视频| 欧美日韩激情一区| 色婷婷在线播放| 久久波多野结衣| 蜜桃精品视频在线| 欧美日韩在线国产| 日韩精品在线看| 久久精品资源| 天天做天天躁天天躁| www.av精品| 最近中文字幕在线免费观看| 久久久999国产精品| 精品久久ai电影| 一区二区三区 日韩| 亚洲国产综合91精品麻豆| 韩日在线视频| 99久久99久久精品国产片| 亚洲尤物影院| 三级黄色在线观看| 日韩精品欧美国产精品忘忧草 | 久久国产免费看| 日韩精品成人一区| 最近的2019中文字幕免费一页| 91综合精品国产丝袜长腿久久| 乱子伦视频在线看| 一区二区三区四区在线免费观看| 欧美zozo| 国产精品一码二码三码在线| 久久久久久一区二区| 久草视频中文在线|