การใช้งานสื่อคิดเป็นสัดส่วนที่มากของเวลาที่ผู้ใช้ใช้บนอุปกรณ์เคลื่อนที่ ซึ่งต้องอาศัยประสบการณ์การเล่นที่ราบรื่น มีประสิทธิภาพสูง และคำนึงถึงอายุการใช้งานแบตเตอรี่ นอกจากนี้ ผู้ใช้ยังคาดหวังว่าประสบการณ์เหล่านี้จะเปลี่ยนผ่านระหว่างอุปกรณ์ต่างๆ ได้อย่างราบรื่น ตั้งแต่โทรศัพท์ไปจนถึงทีวีใน Living Room และอื่นๆ
สำรวจว่าการอัปเดตสถาปัตยกรรมใน Media3 และ ExoPlayer ช่วยให้การเล่นราบรื่นในอุปกรณ์รูปแบบต่างๆ ได้อย่างไร การพูดคุยจะเน้นไปที่การสร้างอินเทอร์เฟซที่ปรับเปลี่ยนตามอุปกรณ์ด้วยชุดเครื่องมือ Media3 Compose UI, การกำจัดบัฟเฟอร์ในฟีดโดยใช้ PreloadManager ใหม่ และการขยายแอปไปยังหน้าจอ Living Room และ Android XR ด้วย CastPlayer และเสียงรอบทิศทาง
ข้อความถอดเสียง
ตกลง
ขอต้อนรับทุกคน
ฉันชื่อ Toni
ฉันเป็นวิศวกรในทีมสื่อของ Android
และเราจะพูดคุยกับ Kristina และ Donovan เกี่ยวกับวิธีที่ Jetpack Media3 สามารถช่วยตอบสนองความต้องการด้านสื่อทั้งหมดของคุณบน Android
Media3 เป็นไลบรารีสนับสนุนของ Android สำหรับทุกอย่างที่เกี่ยวข้องกับสื่อ ไม่ว่าจะเป็นการเล่นด้วย ExoPlayer, การแชร์สื่อด้วยเซสชันสื่อและ Cast, การแก้ไขสื่อด้วย Transformer หรือการตรวจสอบสื่อ เช่น การแยกเฟรมหรือข้อมูลเมตา
Media3 ครอบคลุมพื้นที่ทั้งหมดนี้ในกลุ่มคลังเดียว
เนื่องจากเป็นไลบรารีสนับสนุน จึงรวมอยู่ในแอปของคุณ
ซึ่งหมายความว่าเราสามารถรับประกันความสอดคล้องได้ เพื่อให้โค้ดเดียวกันทำงานในระบบปฏิบัติการทุกเวอร์ชันและอุปกรณ์ทุกเครื่อง
นอกจากนี้ยังช่วยให้สามารถอัปเดตได้
การแก้ไขข้อบกพร่องทุกรายการที่เราทำจะครอบคลุมไปถึง API 24 และไม่ได้ผูกกับรอบการเผยแพร่ระบบปฏิบัติการใดๆ
และในฐานะไลบรารีสนับสนุน เราสามารถอนุญาตตัวเลือกการปรับแต่งที่หลากหลาย ซึ่งเป็นไปไม่ได้ในเฟรมเวิร์ก Android
และในฐานะไลบรารีบน GitHub เราเป็นโอเพนซอร์สเป็นอันดับแรก
ซึ่งหมายความว่าการเปลี่ยนแปลงทุกอย่างที่เราทำจะพร้อมใช้งานใน GitHub ทันที
เรามี Issue Tracker ที่ใช้งานอยู่ และเรายอมรับ Pull Request
สิ่งสำคัญที่ต้องเน้นย้ำใน Use Case ทั้งหมดนี้คือเราให้ความสำคัญกับรายละเอียดทุกระดับ ตั้งแต่องค์ประกอบ UI ระดับสูงไปจนถึง API ที่พร้อมใช้งานทันทีแบบง่ายๆ รวมถึงตัวเลือกการปรับแต่งเชิงลึก การรองรับรูปแบบและมาตรฐานที่หลากหลาย ตลอดจนประสิทธิภาพและประสิทธิผลที่รองรับทั้งหมดนี้
Media3 ครอบคลุมพื้นที่ทั้งหมดนี้
วันนี้เราจะพยายามเจาะลึกหัวข้อเหล่านี้บางหัวข้อ
เราจะพูดถึงโมดูล Compose UI ใหม่, CastPlayer ใหม่ที่ช่วยให้การแชร์กับเซสชันสื่อทำได้ง่ายขึ้น, ไปป์ไลน์การแก้ไขที่ปรับปรุงใหม่พร้อมเอฟเฟกต์ขั้นสูงใหม่, เครื่องมือใหม่ในการดึงข้อมูลเมตาและเฟรม, เทคโนโลยีการสตรีมใหม่ๆ โดยเฉพาะวิดีโอและเสียง Eclipsa และยูทิลิตีคุณภาพของประสบการณ์การใช้งานจำนวนมากสำหรับการโหลดล่วงหน้า การลากแถบเวลา และการประหยัดแบตเตอรี่
ตอนนี้เรามาเจาะลึกหัวข้อแรกกันเลย นั่นคือวิธีที่ Media3 ช่วยสร้างองค์ประกอบ UI สำหรับสื่อ
เมื่อเร็วๆ นี้เราได้เผยแพร่โมดูลใหม่ 2 โมดูล ได้แก่ Media3 UI Compose และ Media3 UI Compose Material 3
ที่นี่ คุณจะเห็นคอมโพสิเบิลเพลเยอร์ระดับสูง เนื่องจากเปิดตัวใน Media3 1.10 โดยไม่มีการแก้ไขเพิ่มเติม
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}พร้อมใช้งานทันที
โดยใช้เพียงเพลเยอร์ Media3 เท่านั้น
ในตัวอย่างนี้คือ ExoPlayer ซึ่งปกติแล้วคุณจะมีอยู่ในโมเดลมุมมอง
แต่ก็อาจเป็นอย่างอื่นได้ เช่น CastPlayer, ตัวควบคุมสื่อ หรือเพลเยอร์อื่นๆ ที่คุณมีในแอป
โปรดทราบว่า ExoPlayer เป็นแหล่งข้อมูลที่เชื่อถือได้
ซึ่งมีข้อมูลทั้งหมด และคอมโพสิเบิลเพลเยอร์จะดูแลการแสดงผล UI และเชื่อมต่อทุกอย่างเข้าด้วยกัน
แต่จะเกิดอะไรขึ้นหากคุณต้องการปรับแต่ง UI นี้ ปุ่มใหม่ ตำแหน่งที่แตกต่างกัน สไตล์ที่แตกต่างกัน การวางซ้อนข้อความ
Composable ของโปรแกรมเล่นสร้างขึ้นจากองค์ประกอบที่ใช้สร้างสรรค์
คุณสามารถเปลี่ยนแบตเตอรี่ได้
คุณปรับแต่งแต่ละบล็อกได้ หรือจะใช้บล็อกแบบสแตนด์อโลนที่อื่นในแอปก็ได้
เช่น คุณสามารถปรับแต่งด้านบน ตรงกลาง และbottomControlsแยกกันได้
นอกจากนี้ คุณยังปรับแต่งลักษณะของชัตเตอร์ซึ่งจะแสดงเมื่อวิดีโอไม่เล่นหรือการวางซ้อนข้อผิดพลาดได้ด้วย
แต่นี่เป็นเพียงชั้นแรกเท่านั้น
นอกจากนี้ หน้าปัดแบบ Default Center และ bottomControls ยังมีช่องที่ละเอียดมากขึ้นซึ่งคุณปรับแต่งได้
เช่น คุณสามารถสลับปุ่มตรงกลางออกโดยปล่อยให้องค์ประกอบอื่นๆ ทั้งหมดเหมือนเดิม หรือเพิ่มองค์ประกอบด้านบนหรือด้านล่างแถบเลื่อน
Media3 Compose มีองค์ประกอบทั้งหมดที่คุณต้องใช้ในการดำเนินการนี้
ตัวอย่างเช่น เรามีปุ่มต่างๆ มากมาย เช่น ปุ่มเล่น/หยุดชั่วคราว โหมดเล่นซ้ำ โหมดสุ่มเพลง ปุ่มปิดเสียง และอื่นๆ
จากนั้นจะมีตัวบ่งชี้ความคืบหน้า เช่น แถบเลื่อนและข้อความตำแหน่งที่แสดงบนหน้าจอ
นอกจากนี้ยังมีคอนเทนเนอร์และภาพซ้อนทับจำนวนมากด้วย
คอมโพสิเบิลเพลเยอร์ระดับสูงที่ฉันแสดงให้คุณเห็นเป็นเพียงตัวอย่างหนึ่ง แต่ยังมีตัวอย่างอื่นๆ อีก เช่น การวางซ้อนข้อผิดพลาดหรือคำบรรยาย และอื่นๆ อีกมากมาย
นอกจากนี้ เรายังมีคอมโพเนนต์อื่นๆ ที่มีประโยชน์ เช่น ตัวควบคุมขนาดเล็กนี้
คุณสามารถวางไว้ใน Bottom Sheet ขณะที่ผู้ใช้ไปยังส่วนต่างๆ ของแอป
และเราจะไม่หยุดเพียงเท่านี้
เรากำลังขยายคลังนี้เพิ่มเติมอย่างต่อเนื่อง เช่น การเพิ่มการควบคุมการแก้ไขใหม่ๆ อย่างแถบเลื่อนการตัดคลิปที่จะเปิดตัวเร็วๆ นี้
ตอนนี้เรามาดูการปรับแต่งนี้ในการใช้งานจริงเพื่อดูวิธีใช้องค์ประกอบที่ใช้สร้างสรรค์เหล่านี้กัน
Player(viewModel.exoPlayer)สมมติว่าเราต้องการย้ายข้อความตำแหน่งและระยะเวลาซึ่งปัจจุบันอยู่ข้างแถบเลื่อนไปไว้ด้านบน
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)สิ่งที่เราทำได้คือลบล้างbottomControlsเหล่านี้
เรายังคงใช้คอมโพเนนต์เริ่มต้นได้ แต่เราตั้งค่าช่องด้านซ้ายและขวาให้ว่างเปล่าเพื่อให้หายไป
แต่เราจะวางข้อความตำแหน่งและระยะเวลาไว้ด้านบนในช่องด้านบนพร้อมตัวคั่นที่กำหนดเองเพื่อให้ดูน่าสนใจยิ่งขึ้น
และตอนนี้ สมมติว่าเราต้องการนำภาพซ้อนทับ centerControls ออกทั้งหมด และวางปุ่มเล่นไว้เหนือแถบเลื่อนพร้อมตัวควบคุมความเร็วและปุ่มปิดเสียงแทน
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}ซึ่งเป็นแนวคิดเดียวกันกับที่กล่าวไปข้างต้น
เราสามารถตั้งค่า centerControls ให้ว่างเปล่าเพื่อให้หายไป และเราสามารถขยายช่องเหนือแถบเลื่อนด้วยพื้นที่เพิ่มเติมและปุ่ม 3 ปุ่มที่เราต้องการแสดง
โปรดทราบว่าปุ่มเหล่านี้มาจากไลบรารีเอง คุณจึงไม่ต้องใส่โค้ดเพิ่มเติมลงในปุ่ม
เพียงเท่านี้ก็เรียบร้อย
และคุณสามารถดำเนินการต่อตามเส้นทางนี้เพื่อจัดรูปแบบองค์ประกอบเหล่านี้ได้เช่นกัน
หากต้องการเปลี่ยนไอคอนปุ่มปิดเสียง แต่ยังคงฟังก์ชันการทำงานไว้ ก็ทำได้ในลักษณะเดียวกัน
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)อย่างที่เห็นตรงนี้ คุณสามารถกำหนด Painter ที่กำหนดเองสำหรับปุ่มและเลือกไอคอนด้วยตนเองได้
คอมโพเนนต์จะให้ข้อมูลเมตาบางอย่างแก่คุณ ดังนั้นสิ่งที่คุณต้องทำก็คือดูสถานะ เช่น showMuted ในตัวอย่างนี้ เพื่อเลือกไอคอนที่ถูกต้อง
และนี่คือประโยชน์หลักของ Media3 Compose
คุณสามารถมุ่งเน้นที่รูปลักษณ์ของแอป และ Media3 จะจัดการส่วนที่เหลือให้เอง
โดยจะอ่านสถานะจากโปรแกรมเล่น โต้ตอบกับโปรแกรมเล่น และจัดการการเปลี่ยนภาพและภาพเคลื่อนไหวทั้งหมดในระหว่างนั้น
ตอนนี้เราจะเปลี่ยนจาก UI ไปเป็นการแชร์สื่อ
คุณสามารถแชร์สถานะการเล่นกับแอปอื่นๆ ในอุปกรณ์เดียวกันหรือเชื่อมต่อกับอุปกรณ์ระยะไกลได้
วิธีหลักในการเผยแพร่การเล่นที่ดำเนินอยู่คือการใช้ MediaSession
ซึ่งจะทำให้ผู้อื่นเห็นสถานะและข้อมูลเมตาของการเล่นของคุณ และควบคุมการเล่นของคุณได้
ซึ่งเป็นวิธีที่ Gemini, การแจ้งเตือนสื่อของ UI ระบบ, Cast, Wear OS หรือแอปที่ใช้ร่วมกันของ Android Auto สื่อสารกับแอปของคุณ
MediaSession จะรวมเพลเยอร์ Media3 ไว้ และการสร้างเพลเยอร์นั้นทำได้ง่ายมากเมื่อคุณมีเพลเยอร์แล้ว
MediaSession.Builder(context, player).build()ซึ่งอาจเป็น ExoPlayer หรือเพลเยอร์อื่นๆ ที่คุณมีในแอป เช่นเดียวกับ Compose UI
หากคุณเล่นในเบื้องหลัง โดยปกติแล้วคุณจะโฮสต์สิ่งนี้ไว้ใน MediaSessionService หรือ MediaLibraryService ซึ่งจะจัดการวงจรทั้งหมดให้คุณโดยอัตโนมัติ รวมถึงสิ่งต่างๆ เช่น การแจ้งเตือน
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}แต่หากคุณมีแอปสื่อ คุณก็น่าจะทราบเรื่องนี้อยู่แล้ว
สิ่งใหม่ที่น่าตื่นเต้นที่เราอยากแสดงให้คุณเห็นคือ CastPlayer ที่เขียนขึ้นใหม่
Google Cast ช่วยให้คุณโอนการเล่นในเครื่องไปยังอุปกรณ์ระยะไกล เช่น Android TV, Chromecast หรือลำโพงอัจฉริยะได้
ก่อนหน้านี้เราเคยเผยแพร่ Cast SDK แยกต่างหาก ซึ่งตอนนี้อยู่ในโหมดการบำรุงรักษา
แต่ Media3 Cast จะมาแทนที่โดยสมบูรณ์เนื่องจากมีการผสานรวมที่ง่ายและมีประสิทธิภาพมากขึ้น
องค์ประกอบที่สำคัญที่สุดคือ CastPlayer ซึ่งใช้อินเทอร์เฟซเพลเยอร์มาตรฐานเดียวกันกับ ExoPlayer
แม้ว่า ExoPlayer จะใช้สำหรับการเล่นในเครื่อง แต่ CastPlayer เป็นอินสแตนซ์การมอบสิทธิ์ที่สามารถเล่นในเครื่องผ่าน ExoPlayer หรือเล่นจากระยะไกลผ่านโปรโตคอล Cast ก็ได้
และจะจัดการทุกอย่างให้คุณ
โดยจะคอยฟังการเปลี่ยนแปลงของอุปกรณ์เอาต์พุต สร้างการเชื่อมต่อ ซิงค์สถานะ และโอนการเล่นให้คุณเมื่อจำเป็น
และด้วยเหตุนี้ การผสานรวม CastPlayer เข้ากับแอปของคุณจึงเป็นเรื่องง่ายมาก
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()แทนที่จะสร้าง ExoPlayer และแชร์กับ MediaSession แบบนี้ คุณจะใช้ CastPlayer แทน และนั่นก็คือทั้งหมด
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()สิ่งเดียวที่คุณต้องทำคือเริ่มต้นระบบ Cast ก่อนที่จะใช้ระบบดังกล่าวในแอป
โปรดทราบว่า CastPlayer นี้จะสร้าง ExoPlayer ภายในสําหรับการเล่นในเครื่อง ดังนั้นหากต้องการปรับแต่งเพลเยอร์ในเครื่องเพิ่มเติม คุณสามารถสร้าง ExoPlayer ของคุณเองและส่งเป็นเพลเยอร์ในเครื่องไปยัง CastPlayer นี้ได้
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()และนี่ก็เป็นหลักการเดียวกันกับ Media3 อีกครั้ง
เรามีองค์ประกอบพื้นฐานของ API ที่ใช้งานง่ายและพร้อมใช้งานทันที รวมถึงตัวเลือกการปรับแต่งทั้งหมดที่คุณต้องการเพื่อปรับแต่งเพิ่มเติม
และคุณอาจสงสัยว่าผู้ใช้จะสลับระหว่างการเล่นในเครื่องและการเล่นระยะไกลในการตั้งค่านี้ได้อย่างไร
โดยสามารถใช้งานได้ทันทีด้วยกล่องโต้ตอบตัวสลับเอาต์พุตของระบบในการแจ้งเตือนของสื่อ
ซึ่งเป็นปุ่มเล็กๆ ที่มุมนั้น
แต่จะทำอย่างไรหากต้องการแสดงปุ่มแคสต์ในหน้าจอการเล่นเพื่อให้ค้นพบได้ง่ายขึ้น
จำ UI ของ Compose ที่ปรับแต่งเองซึ่งเราเพิ่งสร้างเมื่อไม่กี่นาทีก่อนได้ไหม
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)นอกจากนี้ Media3 Cast ยังมีปุ่มที่พร้อมใช้งานทันทีซึ่งคุณสามารถวางไว้ที่นั่นได้
ซึ่งทำได้ง่ายๆ เพียงเพิ่ม MediaRouteButton สำเร็จรูปที่มีอยู่แล้วลงใน topControls เช่น เราไม่จำเป็นต้องตั้งค่าพารามิเตอร์เพิ่มเติมใดๆ อีก
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)และการคลิกปุ่มนี้จะเปิดกล่องโต้ตอบตัวสลับเอาต์พุตของระบบเดียวกัน จากนั้น CastPlayer จะโอนการเล่นให้คุณ
และคุณจะเห็นว่าฉันเชื่อมต่อ CastPlayer กับ UI ที่นี่เพื่อให้แน่ใจว่าสิ่งที่เกิดขึ้นจากระยะไกลจะแสดงใน UI ในเครื่องของคุณ
และในส่วนนี้ ฉันขอส่งต่อให้ Kristina มาพูดถึงกรณีการใช้งานการแก้ไข
ขอขอบคุณ
สวัสดี ตอนนี้เรามาดูเรื่องการตัดต่อและเอฟเฟกต์ขั้นสูงกัน
Media3 API ประกอบด้วยคอมโพเนนต์หลายรายการ
ก่อนอื่น Transformer เป็นจุดแรกเข้าหลักสำหรับ API การแก้ไขของเรา
Transformer จะประมวลผล แปลง และส่งออกสื่อไปยังไฟล์
ถัดไปคือ CompositionPlayer ซึ่งแสดงตัวอย่างองค์ประกอบสื่อแบบเรียลไทม์
ซึ่งจะช่วยให้คุณดูตัวอย่างและปรับแต่งการแก้ไขก่อนส่งออกขั้นสุดท้ายได้
จากนั้นก็มีเอฟเฟกต์
ซึ่งจะช่วยให้คุณควบคุมการประมวลผลแบบเฟรมต่อเฟรมได้อย่างเต็มที่เมื่อใช้เอฟเฟกต์ภาพแบบง่ายหรือเพิ่มการวางซ้อนกราฟิก
สุดท้ายคือ Muxer
โดยทำหน้าที่เป็นขั้นตอนเอาต์พุตสุดท้ายในไปป์ไลน์ของเรา ซึ่งจะรับแทร็กเสียง วิดีโอ และข้อมูลเมตาที่ประมวลผลแล้ว และเขียนลงในคอนเทนเนอร์ MP4 ที่ได้มาตรฐาน
เราใช้เวลาตลอดปีที่ผ่านมาในการคิดใหม่และสร้างไปป์ไลน์การตัดต่อวิดีโอขึ้นมาใหม่ตั้งแต่ต้น และวันนี้เราตื่นเต้นที่จะได้แชร์การปรับปรุงที่สำคัญ
เหตุใดเราจึงสร้างไปป์ไลน์ใหม่
สถาปัตยกรรมเดิมจึงทำให้เกิดความท้าทายหลายประการ
ประการแรก ระบบไม่สามารถเผยแพร่ข้อมูลเมตาในทุกขั้นตอนการประมวลผล
โดยเชื่อมโยงกับ OpenGL อย่างลึกซึ้ง ซึ่งจำกัดความยืดหยุ่นสำหรับเครื่องมือกราฟิกสมัยใหม่
นอกจากนี้ API ที่ซับซ้อนยังทำให้การปรับแต่งและการบำรุงรักษาเป็นอุปสรรคที่ยากลำบากสำหรับนักพัฒนาซอฟต์แวร์
เราจึงสร้างไปป์ไลน์การประมวลผลใหม่ทั้งหมดเพื่อจัดการกับความท้าทายเหล่านี้
อย่างแรก การจัดทีมที่ซับซ้อนซึ่งประกอบด้วยหลายชิ้นงานทำได้ง่ายขึ้นมากแล้วในตอนนี้
ประการที่ 2 การแยกออกจาก OpenGL ช่วยให้คุณมีอิสระอย่างเต็มที่ในการใช้เฟรมเวิร์กกราฟิกสมัยใหม่ เช่น WebGPU เป็นต้น
สุดท้ายนี้ Media3 จะจัดการตัวถอดรหัสและตัวเข้ารหัสโดยตรง เพื่อให้มั่นใจว่าอุปกรณ์ทั้งหมดจะมีความสอดคล้องและมีประสิทธิภาพมากขึ้น
มาเจาะลึกวิธีการทำงานของไปป์ไลน์นี้กัน
ตัวถอดรหัสจะแสดงผลเฟรมไปยังบัฟเฟอร์ฮาร์ดแวร์ที่จัดการโดย Media3 โดยตรง
ตัวรวมเฟรมจะประสานงานเฟรมในหลายลำดับ เพื่อให้โปรเซสเซอร์เฟรมที่กำหนดเองมีสิทธิ์เข้าถึงโดยตรงในการอ่านและประมวลผลบัฟเฟอร์เหล่านั้นจากการส่งไปยังโปรแกรมเปลี่ยนไฟล์
อินเทอร์เฟซ FrameProcessor ทำหน้าที่เป็นจุดขยายหลักสำหรับการประมวลผลระดับแอป
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}แอปพลิเคชันจะแจ้งเตือน Media3 ผ่านอินเทอร์เฟซ Listener ทุกครั้งที่มีการประมวลผลเฟรม โดยส่งผ่านรั้วการซิงค์ข้อมูล เพื่อให้ Media3 ทราบอย่างแน่ชัดว่าสามารถนำบัฟเฟอร์กลับมาใช้ใหม่ได้อย่างปลอดภัยเมื่อใด
หมายเหตุเกี่ยวกับหน่วยความจำแบบรวม: การให้ Media3 เป็นเจ้าของบัฟเฟอร์ฮาร์ดแวร์จะช่วยให้แอปอ่านและแก้ไขข้อมูลได้โดยตรงในตำแหน่งนั้น
แนวทางแบบ Zero-Copy นี้ช่วยลดภาระการใช้หน่วยความจำและทำให้การประมวลผลรวดเร็วเป็นพิเศษ
Adobe เป็นหนึ่งในนักพัฒนาแอปที่ใช้ประโยชน์จาก API การแก้ไขใหม่
ทีม Adobe Premiere ใช้ API การตัดต่อ Media3 เป็นรากฐานของโครงสร้างพื้นฐานการตัดต่อสื่อเพื่อให้มั่นใจว่ามีความเข้ากันได้มากขึ้นในระบบนิเวศของ Android ขณะเดียวกันก็ยังคงความยืดหยุ่นที่จำเป็นในการใช้ไปป์ไลน์ที่กำหนดเองและ WebGPU ของตนเอง
ต่อไป เรายินดีที่จะประกาศว่าเรากำลังเปิดตัวเอฟเฟกต์ใหม่ซึ่งเป็นส่วนหนึ่งของ Media3 เวอร์ชัน 1.12
เริ่มจากการเพิ่มประสิทธิภาพวิดีโอ
สถาปัตยกรรมของเราอาศัยการตั้งค่าแบบโมดูลาร์
แอปพลิเคชันของคุณผสานรวมกับบริการ Play Services สำหรับเอฟเฟกต์ Media3 ซึ่งเชื่อมต่อกับโมดูลการเพิ่มประสิทธิภาพบริการ Play Services
โมดูลการปรับปรุงวิดีโอประกอบด้วยความสามารถในการแมปโทน การลดนอยส์ การปรับให้คมชัด และความละเอียดสูง
การผสานรวม Video Enhance เข้ากับไปป์ไลน์ของคุณใช้โค้ดเพียงไม่กี่บรรทัด
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)เมื่อใช้ EnhancementSessionDecorator.Builder คุณจะเลือกเปิดใช้ความสามารถต่างๆ เช่น การแมปโทน การปรับให้คมชัด หรือการเพิ่มความละเอียดของรูปภาพ และส่ง frameProcessorFactory และตัวช่วยบัฟเฟอร์ฮาร์ดแวร์ที่ได้ไปยัง Transformer.Builder ได้
อีกหนึ่งเอฟเฟกต์ที่น่าตื่นเต้นซึ่งจะมาใน Media3 1.12 คือเสียงระดับสตูดิโอ
ฟีเจอร์เสียงระดับสตูดิโอจะวิเคราะห์แทร็กเสียงของวิดีโอโดยอัตโนมัติและแยกออกเป็นองค์ประกอบต่างๆ เช่น เสียงพูด เสียงแวดล้อม และเพลง
ครีเอเตอร์สามารถลดเสียงรบกวนจากลมหรือฝูงชนได้อย่างง่ายดาย พร้อมกับทำให้เสียงพูดและเพลงชัดเจนขึ้น
หากต้องการผสานรวมเสียงระดับสตูดิโอ ให้ส่งตัวประมวลผลเสียงระดับสตูดิโอภายในออบเจ็กต์เอฟเฟกต์
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()ซึ่งจะแนบโปรเซสเซอร์เสียงกับรายการสื่อของคุณ
เมื่อสร้างโปรเซสเซอร์เสียง Studio Sound ให้แนบ Callback ของ Listener เพื่อรับการแมปช่องเสียงที่วิเคราะห์แล้วผ่าน onResult
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)ทำซ้ำในแต่ละช่องทางเพื่อดึงดัชนีช่องทาง ประเภทเสียงที่ระบุ และป้ายกำกับที่อ่านได้เมื่อผลลัพธ์ของการดำเนินการพร้อมใช้งาน
มาฟังเสียงระดับสตูดิโอในการใช้งานจริงกัน
ผลกระทบแรก ที่รัก ได้โปรดอย่าร้องไห้เลย
เรากำลังตัดเพื่อเตรียมอาหารเย็นให้คุณ
คุณจะได้ยินเสียงทารกร้องไห้เป็นพื้นหลัง และตอนนี้ก็ใช้เอฟเฟกต์แล้ว
ใช่ อย่างที่คุณได้ยิน ไม่มีเสียงเด็กร้องไห้ในพื้นหลังเลย ต้องขอบคุณเอฟเฟกต์เสียงสตูดิโอ
ตอนนี้เรามาเจาะลึกเรื่องการดึงข้อมูลเมตากัน
เราจะมาดูวิธีที่ Media3 ช่วยให้คุณอ่านข้อมูลเมตาของคอนเทนเนอร์และดึงเฟรมออกมาได้อย่างง่ายดาย
โดยเริ่มจาก MetadataRetriever ซึ่งผสานรวมกับโครูทีน Kotlin ได้อย่างราบรื่นเพื่อให้โค้ดสะอาดและไม่บล็อก
โดยมีการออกแบบที่สะอาดตาและมีแนวโน้มที่จะเกิดข้อผิดพลาดน้อยลง โดยใช้ตัวดึงข้อมูลที่เปลี่ยนแปลงไม่ได้ 1 ตัวต่อรายการสื่อ
ซึ่งทำงานได้อย่างสอดคล้องกันใน API ทุกเวอร์ชันและอุปกรณ์ทุกเครื่อง
และรองรับรูปแบบสื่อที่หลากหลายตั้งแต่แกะกล่อง
ต่อไปนี้เป็นตัวอย่างการใช้งานจริงโดยใช้ MetadataRetriever
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}เมื่อใช้โครูทีน Kotlin คุณจะดึงข้อมูลระยะเวลา ตรวจสอบรูปแบบแทร็กและข้อมูลเมตาแบบอะซิงโครนัสได้ทั้งหมดภายในบล็อกเดียวที่สะอาดตา
ตอนนี้เรามาพูดถึง FrameExtractor กัน
ซึ่งช่วยให้ดึงเฟรมวิดีโอที่ถอดรหัสแล้วจากรายการสื่อได้อย่างรวดเร็วและมีประสิทธิภาพ
โดยจะจัดการทรัพยากรที่สำคัญ เช่น ตัวถอดรหัสโดยอัตโนมัติ ช่วยให้คุณใช้เอฟเฟกต์ภาพหรือการแมปโทนสี HDR ได้ และให้คุณควบคุมการค้นหาแบบละเอียดเพื่อให้ได้เฟรมที่ต้องการอย่างแม่นยำ
ต่อไปนี้คือลักษณะของการดึงเฟรมในโค้ด
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}คุณสร้างอินสแตนซ์ที่มีตัวเลือกต่างๆ เช่น พารามิเตอร์การกรอวิดีโอ การลดขนาด GPU เอฟเฟกต์ และการรองรับ HDR จากนั้นรอภาพปกและเฟรมที่เฉพาะเจาะจงแบบอะซิงโครนัสโดยใช้การเรียกใช้แบบระงับที่สะอาด
และตอนนี้ขอส่งต่อให้ Donovan
ขอบคุณ Kristina
ต่อไป เราจะพูดถึงการรองรับเทคโนโลยีสตรีมมิงพื้นฐานบางอย่างที่เราเพิ่มเข้ามาใน Media3 ซึ่งรวมถึงการรองรับรูปแบบต่างๆ เช่น วิดีโอ Eclipsa และระบบเสียง Eclipsa
ดังนั้น ความพยายามหลักใน Media3 คือการก้าวนำมาตรฐานการสตรีม เพื่อให้คุณไม่ต้องเขียนไปป์ไลน์สื่อใหม่ทุกครั้งที่มีข้อกำหนดใหม่
ตัวอย่างสิ่งที่เราเปิดตัวเมื่อเร็วๆ นี้มีดังนี้
เรามีฟีเจอร์การควบคุมเนื้อหา ซึ่งช่วยให้สลับเส้นทาง CDN แบ็กเอนด์แบบไดนามิกได้ระหว่างสตรีม
เรามีการรองรับการแทรกโฆษณาตามคำแนะนำของเซิร์ฟเวอร์เพื่อการต่อโฆษณาที่ปรับตามโปรไฟล์ของผู้ใช้อย่างราบรื่น
นอกจากนี้ เรายังได้เปิดตัวการรองรับ AV1 ในจุดเข้าใช้งานหลายจุด ซึ่งรวมถึงการรองรับ AV1 HDR อย่างเต็มรูปแบบ
เรามีวิดีโอระดับมืออาชีพสำหรับ Android เพื่อการจับภาพและตัดต่อที่มีความแม่นยำสูง
นอกจากนี้ เรายังเพิ่มการรองรับรูปภาพเคลื่อนไหวทั้งในรูปแบบ JPEG และ HEIC ด้วย
และแน่นอนว่าเรากำลังเพิ่มการรองรับวิดีโอ Eclipsa และระบบเสียง Eclipsa ซึ่งเราจะพูดถึงเพิ่มเติมในเร็วๆ นี้
ข่าวดีก็คือระบบจะรองรับฟีเจอร์ทั้งหมดนี้โดยอัตโนมัติเมื่อเปิดตัวการรองรับอุปกรณ์
ตอนนี้เรามาดูวิวัฒนาการขั้นถัดไปของ HDR, Eclipsa Video และปัญหาหลักที่พยายามแก้ไขกัน
ดังนั้น รูปแบบ HDR ก่อนหน้านี้จึงให้ข้อมูลเมตาเชิงอธิบาย ซึ่งจะกำหนดลักษณะที่ฉากควรจะเป็น แต่ไม่ได้บอกจอแสดงผลจริงๆ ว่าจะแสดงผลอย่างไร
เรามาเริ่มจากพื้นฐานกันก่อน
สำหรับสภาพแวดล้อมที่แตกต่างกัน เรามีคำแนะนำที่แตกต่างกันสำหรับความสว่างหน้าจอโดยเฉลี่ย เพื่อให้คุณดูเนื้อหาได้อย่างชัดเจนและสบายตา
ดังนั้นในห้องที่มีแสงสลัว ช่วงดังกล่าวจะอยู่ระหว่าง 100 ถึง 300 นิต
และแน่นอนว่าเมื่อคุณอยู่ในสภาพแวดล้อมที่สว่างขึ้นเรื่อยๆ หน้าจอจะต้องปรับตามและสว่างขึ้นเพื่อให้คุณเห็นเนื้อหาได้อย่างชัดเจน จนถึงเมื่อคุณดูโทรศัพท์ในที่ที่มีแสงแดดส่องโดยตรง หน้าจออาจต้องสว่างถึง 1,000, 2,000 หรือมากกว่านั้นเพื่อให้คุณดูเนื้อหาได้
SDR แบบดั้งเดิมจะทำงานในช่วง 100 ถึง 300 นิต
และแน่นอนว่า HDR ไม่ได้มีไว้เพื่อเพิ่มความสว่างของหน้าจอโดยรวม
คุณต้องการขยายช่วงไดนามิกเพื่อให้มีไฮไลต์ที่ปรับให้เหมาะสมกับสิ่งต่างๆ เช่น ไฟและแสงแดด
อย่างไรก็ตาม สมาร์ทโฟนทำให้เกิดความซับซ้อนเล็กน้อย เนื่องจากเราพกติดตัวไปทุกที่ ไม่ว่าจะเป็นห้องมืดๆ ในบ้านหรือชายหาดที่สว่างสดใส
ซึ่งเมื่อรวมกับข้อเท็จจริงที่ว่าแอปจำนวนมากผสมผสานองค์ประกอบ UI แบบ SDR เข้ากับเนื้อหา SDR และเนื้อหา HDR ในฟีดแบบผสม ก็อาจทำให้เกิดเอฟเฟกต์ที่สว่างจ้าจนแสบตาซึ่งคุณอาจพบได้ในบางครั้ง โดยเฉพาะอย่างยิ่งหากคุณเลื่อนดูในช่วงดึก
เราจึงเห็นได้ว่าทำไม HDR จึงคุ้มค่าที่จะใช้
ซึ่งจะช่วยให้เนื้อหาดูดีในสภาพแวดล้อมที่หลากหลายมากขึ้น
และเรายังพบว่าการมีส่วนร่วมของผู้ใช้เพิ่มขึ้นเมื่อเปิด HDR
แต่ปัญหาคือรูปแบบ HDR ที่มีอยู่ไม่ได้คำนึงถึงสภาพแวดล้อมเหล่านี้
นี่คือจุดที่ Eclipsa Video โดดเด่นอย่างแท้จริง
หยุดรอเสียงหัวเราะ
ขอขอบคุณ
ขอขอบคุณ
ดังนั้น Eclipsa Video จึงให้ข้อมูลเมตาที่กำหนดไว้แทนข้อมูลเมตาที่สื่อความหมาย
ซึ่งสร้างขึ้นจาก 3 เสาหลัก
เสาแรกคือหมุดยึดสีขาวสำหรับอ้างอิง
ดังนั้น การดำเนินการนี้จะล็อกความสว่างมาตรฐานไว้ที่จุดสีขาว SDR ที่ปรับตามสภาพแวดล้อมของจอแสดงผล ซึ่งจะช่วยขจัดความสว่างที่เพิ่มขึ้นอย่างฉับพลัน
เสาหลักที่ 2 คือเกณฑ์พื้นฐาน SDR สำหรับครีเอเตอร์
ซึ่งจะช่วยคงเส้นโค้งการแมปโทนสีที่แน่นอนของครีเอเตอร์กลับไปเป็น SDR
ประการที่ 3 เรามีเส้นโค้งอัตราขยายแบบปรับได้ของเฮดรูม ซึ่งจะปรับขนาดไฮไลต์ตามที่กำหนดเพื่อให้ตรงกับเฮดรูม HDR แบบเรียลไทม์ของจอแสดงผล เพื่อให้เนื้อหาตรงกับสภาพแวดล้อมที่ดู
และฟีเจอร์นี้พร้อมใช้งานใน ExoPlayer แบบสำเร็จรูปใน Media3 1.11 บนอุปกรณ์ที่ใช้ Android 17 ขึ้นไป
นอกจากนี้ Eclipsa Video ยังสร้างขึ้นตามมาตรฐานแบบเปิดเพื่อให้ใช้งานร่วมกันได้ในอุตสาหกรรมต่างๆ
จึงไม่มีตอนไหนเหมาะจะรองรับ HDR เท่าตอนนี้อีกแล้ว
ในด้านเสียง เรามีระบบเสียง Eclipsa ซึ่งเป็นรูปแบบเสียงรอบทิศทาง 3 มิติแบบโอเพนซอร์สและไม่มีค่าสิทธิ
ซึ่งอิงตามข้อกำหนด IAMF ของ Alliance for Open Media
โดยจะรวม Ambisonics แบบอิงตามฉากเข้ากับเสียงแบบอิงตามช่องเป็นสตรีมเดียวที่มีการนำเสนอแบบผสมหลายรายการ
ซึ่งช่วยให้สามารถจัดการเสียงแบบออบเจ็กต์ได้ เช่น การปรับปรุงบทสนทนา และจะทำงานได้กับทุกอย่างตั้งแต่หูฟังสเตอริโอไปจนถึงการตั้งค่าเสียงเซอร์ราวด์
จริงๆ แล้ว Media3 มีเส้นทางการเล่น 2 เส้นทาง
สำหรับ Android 17 ขึ้นไป ExoPlayer และเฟรมเวิร์กเสียงของ Android จะแสดงผลระบบเสียง Eclipsa โดยอัตโนมัติ
และสำหรับ Android 16 และเวอร์ชันก่อนหน้า คุณยังมีตัวเลือกอยู่
คุณเปิดใช้ส่วนขยาย IAMF ได้ผ่านไลบรารีส่วนเสริม Media3 ที่ไม่บังคับ
และในด้านการสร้าง คุณสามารถเขียนแทร็กเสียง IAMF โดยใช้ Muxer ของ Media3
แต่การรองรับรูปแบบคุณภาพสูงเป็นเพียงส่วนหนึ่งเท่านั้น
เราต้องนำเสนอคุณภาพนั้นอย่างมีประสิทธิภาพ
ตอนนี้เราจะพูดถึงวิธีที่ Media3 ช่วยให้ไปป์ไลน์ของเราทำงานได้เร็วขึ้นและมีประสิทธิภาพมากขึ้น
เช่นเดียวกับหลายๆ สิ่งที่เราพูดถึงในวันนี้ การปรับปรุงประสิทธิภาพส่วนใหญ่เหล่านี้พร้อมใช้งานทันทีเพียงแค่อัปเดต Media3 ให้เป็นเวอร์ชันล่าสุด
ตัวอย่างหนึ่งของฟีเจอร์ที่เรามีคือการลดภาระเสียง PCM ซึ่งสามารถกำหนดเส้นทางการเล่นเสียงไปยังหน่วยประมวลผลที่ใช้พลังงานต่ำได้
จากการวิเคราะห์เบื้องต้น เราพบว่าการเล่นเสียงแบบปิดหน้าจอช่วยประหยัดพลังงานได้ถึง 20%
นอกจากการลดภาระการประมวลผลเสียง PCM แล้ว เรายังมีการกำหนดเวลาแบบไดนามิก ซึ่งจะปรับการปลุกการวนซ้ำการแสดงผลภายในของ ExoPlayer ให้สอดคล้องกับ VSYNC และความพร้อมใช้งานของบัฟเฟอร์ตัวถอดรหัส
นอกจากนี้ เรายังมีฟีเจอร์การเตรียมพร้อมโปรแกรมแสดงผล ซึ่งช่วยให้คุณเตรียมพร้อมโปรแกรมแสดงผลวิดีโอรองได้ ซึ่งจะช่วยให้เปลี่ยนเพลย์ลิสต์ได้ทันทีและต่อโฆษณาได้ง่ายขึ้น
นอกจากนี้ เรายังมีฟีเจอร์การตรวจจับเพลเยอร์ที่ค้างและการแคชล่วงหน้า ซึ่งจะตรวจหาบัฟเฟอร์และโปรแกรมแสดงผลที่หยุดทำงานโดยอัตโนมัติ และจำกัดหน่วยความจำที่แคชล่วงหน้าอย่างเป็นระเบียบ
โหมดที่เหมาะสมอีกโหมดหนึ่งที่เรามีคือโหมดการลากแถบเวลา
และโฆษณานี้เหมาะสำหรับแอปที่ผู้ใช้มักจะเลื่อนดูเนื้อหา
ดังนั้นเมื่อเปิดใช้โหมดการลากแถบเวลา การลากแถบค้นหาจะตอบสนองได้ดีและราบรื่นขึ้นมาก ดังที่เห็นที่นี่
ทั้งนี้ การดำเนินการนี้จะเกิดขึ้นเมื่อปิดโหมดการลากแถบเวลา
และทางด้านขวา เราจะเห็นว่าโหมดการลากแถบเวลาเปิดอยู่
ขอขอบคุณ
ขอขอบคุณ
โอเค
ทีนี้เรามาพูดถึงประสิทธิภาพของระบบ Android กัน
เราต้องคำนึงถึงทรัพยากร RAM, CPU และแบตเตอรี่ที่ใช้ร่วมกัน ดังนั้นเมื่อ Android เข้มงวดกับกฎเกี่ยวกับการเพิ่มความปลอดภัยของเสียงและบริการที่ทำงานอยู่เบื้องหน้า แอปที่จัดการบริการดิบและบิตแมปของตนเองจึงมักพบข้อผิดพลาดบางอย่าง
สิ่งที่คุณอาจเคยเห็นคือข้อยกเว้นที่ไม่อนุญาตให้เริ่มบริการที่ทำงานอยู่เบื้องหน้าหรือข้อยกเว้น Binder หน่วยความจำเต็ม
เราจึงได้ปรับปรุงเซสชันบริการสื่อและเครื่องมือจัดการการแจ้งเตือนสื่อ รวมถึงไปป์ไลน์บิตแมปในหลายๆ ด้าน เช่น ป้องกัน Intent ของบริการที่ทำงานอยู่เบื้องหน้าที่ล้าสมัย และจำกัดหน่วยความจำของอาร์ตเวิร์ก เป็นต้น
แต่สิ่งสำคัญสำหรับคุณคือ หากคุณใช้ Media3 และปฏิบัติตามคำแนะนำมาตรฐาน ระบบจะจัดการข้อจำกัดด้านประสิทธิภาพของระบบทั้งหมดให้คุณโดยไม่มีผลกระทบใดๆ
สุดท้ายนี้ เราอยากพูดถึงฟีเจอร์ที่จะพร้อมใช้งานใน Media3 เร็วๆ นี้
เมื่อถึงเวลาเข้ารหัสหรือส่งออกวิดีโอ ความหลากหลายของฮาร์ดแวร์อาจเป็นหนึ่งในความท้าทายที่ยิ่งใหญ่ที่สุด
แอปจำนวนมากใช้การกำหนดค่าตัวแปลงรหัสแบบครอบจักรวาล ซึ่งคุณก็ทราบดีว่าทำให้คุณภาพและประสิทธิภาพไม่ดีเท่าที่ควร
เราจึงสร้าง CodecDB ขึ้นมา
CodecDB สร้างขึ้นจากการวัดผลวิดีโอในชีวิตจริงอย่างครอบคลุมโดยใช้เมตริกคุณภาพที่ได้มาตรฐานและการเปรียบเทียบฮาร์ดแวร์ในอุปกรณ์ Android
มาดูการทำงานจริงกันเลย
ทางด้านซ้าย เรามีเฟรมของวิดีโอที่ถ่ายด้วยโทรศัพท์ซึ่งเราใช้การกำหนดค่าวิดีโอทั่วไปที่อาจใช้ในอุปกรณ์หลายเครื่อง
และทางด้านขวา เราใช้การกำหนดค่าที่เหมาะสมที่สุดในระดับอุปกรณ์ซึ่งได้มาจาก CodecDB
แล้วความแตกต่างอยู่ตรงไหน
หากดูในพื้นที่ที่มีรายละเอียดสูง คุณจะเห็นความแตกต่างด้านคุณภาพที่ชัดเจนที่สุด
ดังนั้นให้ดูความชัดเจนของกิ่งก้านต้นไม้และพุ่มไม้ หรือความคมชัดของขอบอาคาร
ดังนั้นสิ่งที่เราได้รับจากที่นี่คือประสบการณ์การรับชมที่ดีขึ้นมาก ซึ่งใช้ประโยชน์จากความสามารถของอุปกรณ์ที่เฉพาะเจาะจงโดยใช้ประโยชน์จาก CodecDB
และการใช้ CodecDB ในโค้ดก็ค่อนข้างง่าย
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()คุณจะใช้รูปแบบเครื่องมือสร้างสเตจที่คล่องแคล่วซึ่งจะแนะนำคุณตลอดพารามิเตอร์ที่จำเป็นทั้งหมด
ดังนั้นคุณจะต้องระบุกรณีการใช้งาน
ในที่นี้เราจะใช้เพื่อแก้ไข
ซึ่งจะบอก CodecDB ว่าคุณกำลังใช้ไปป์ไลน์การส่งออกหรือการแปลงของ Media3
กรณีการใช้งานอื่นๆ ที่คุณตั้งค่าได้ เช่น การจับภาพด้วยกล้อง หากคุณใช้เพื่อการนั้น
นอกจากนี้ คุณยังส่งความละเอียดและอัตราเฟรมเป้าหมายได้ด้วย
จากนั้น แทนที่จะเดาว่าวิดีโอ 1080p นั้นต้องการความเร็ว 8 เมกะบิตต่อวินาทีหรือ 15 เมกะบิตต่อวินาที คุณก็บอก CodecDB ว่าต้องการใช้ตัวแปลงรหัสใดและขอเป้าหมายคุณภาพการรับรู้
ในกรณีนี้ เราจะใช้ระดับคุณภาพ "ดีมาก"
เบื้องหลังการทำงาน CodecDB จะค้นหาเส้นโค้งการเปรียบเทียบเชิงประจักษ์สำหรับ SOC นั้นๆ และแสดงผลพารามิเตอร์ตัวแปลงรหัสและโปรแกรมเปลี่ยนไฟล์ที่เหมาะสมที่สุด
เราคาดว่า CodecDB จะเปิดตัวใน Media3 เร็วๆ นี้ และเมื่อเปิดตัวแล้ว จะใช้ประเภท Media3 แบบเนทีฟ
ดังนั้นการผสานรวมกับโค้ด Media3 ที่มีอยู่จึงเป็นเรื่องง่าย
ตกลง
เราได้เห็นฟังก์ชันการทำงานที่น่าทึ่งมากมายใน Media3 วันนี้ มาสรุปกันดีกว่า
วันนี้คุณได้เรียนรู้เกี่ยวกับโมดูล Compose UI ใหม่, CastPlayer ใหม่, ไปป์ไลน์การแก้ไขที่ปรับปรุงใหม่ และเอฟเฟกต์ใหม่ๆ มากมายที่คุณใช้ใน Media3 ได้
นอกจากนี้ เรายังเห็นเครื่องมือใหม่ๆ ที่ใช้ดึงข้อมูลเมตาและเฟรมด้วย
นอกจากนี้ เรายังได้พิจารณาการรองรับเทคโนโลยีการสตรีมใหม่ๆ รวมถึงการเพิ่มประสิทธิภาพและยูทิลิตี
Media3 เป็นไลบรารีโอเพนซอร์ส คุณจึงดูวิธีที่เราใช้ฟีเจอร์ทั้งหมดเหล่านี้ได้ในที่เก็บ GitHub
ณ วันนี้ Media3 เวอร์ชันเสถียรล่าสุดคือ 1.11.1
นอกจากนี้ คุณยังดูเวอร์ชัน 1.12.0 ซึ่งปัจจุบันอยู่ในเวอร์ชันอัลฟ่าได้ด้วย
และไปที่ลิงก์สั้นๆ นี้ ซึ่งจะนำคุณไปยังเอกสารประกอบสำหรับนักพัฒนาซอฟต์แวร์เกี่ยวกับ Media3
ขอขอบคุณเป็นอย่างยิ่ง และขอให้สนุกกับช่วงเวลาที่เหลือของ ADS
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()