วิธีลดความล่าช้าในการเริ่ม iOS AVPlayer


115

หมายเหตุสำหรับคำถามด้านล่าง: เนื้อหาทั้งหมดอยู่ในเครื่องของอุปกรณ์ - ไม่มีการสตรีมเครือข่ายเกิดขึ้น วิดีโอมีแทร็กเสียง

ฉันกำลังทำงานกับแอปพลิเคชัน iOS ที่ต้องเล่นไฟล์วิดีโอโดยมีความล่าช้าน้อยที่สุดเพื่อเริ่มคลิปวิดีโอที่เป็นปัญหา น่าเสียดายที่เราไม่ทราบว่าจะมีคลิปวิดีโอใดต่อไปจนกว่าเราจะต้องเริ่มต้นจริง โดยเฉพาะ: เมื่อวิดีโอหนึ่งกำลังเล่นเราจะรู้ว่าชุดต่อไปของคลิปวิดีโอ 10 คลิป (โดยประมาณ) คืออะไร แต่เราไม่รู้ว่าอันไหนกันแน่จนกว่าจะถึงเวลา "ทันที" ที่จะเล่นคลิปถัดไป

สิ่งที่ฉันได้ทำเพื่อดูความล่าช้าในการเริ่มต้นจริงคือการโทรหาaddBoundaryTimeObserverForTimesโปรแกรมเล่นวิดีโอโดยมีช่วงเวลาหนึ่งมิลลิวินาทีเพื่อดูว่าวิดีโอเริ่มเล่นจริงเมื่อใดและฉันใช้ความแตกต่างของการประทับเวลานั้นกับครั้งแรกใน รหัสที่ระบุว่าจะเริ่มเล่นเนื้อหาใด

จากสิ่งที่ฉันได้เห็นมาจนถึงตอนนี้ฉันพบว่าการใช้การรวมกันของการAVAssetโหลดและจากนั้นสร้างAVPlayerItemจากนั้นเมื่อพร้อมแล้วรอAVPlayerStatusReadyToPlayก่อนที่ฉันจะเรียกเล่นมีแนวโน้มที่จะใช้เวลาระหว่าง 1 ถึง 3 วินาทีในการเริ่ม คลิป.

ตั้งแต่นั้นมาฉันได้เปลี่ยนไปใช้สิ่งที่ฉันคิดว่าเทียบเท่าโดยประมาณนั่นคือการโทร[AVPlayerItem playerItemWithURL:]และรอAVPlayerItemStatusReadyToPlayให้เล่น ประสิทธิภาพใกล้เคียงกัน

สิ่งหนึ่งที่ฉันสังเกตคือการโหลดรายการ AVPlayer ครั้งแรกช้ากว่าที่เหลือ ดูเหมือนว่าแนวคิดอย่างหนึ่งคือการเปิดเครื่อง AVPlayer ล่วงหน้าด้วยเนื้อหาสั้น / ว่างเปล่าก่อนที่จะลองเล่นวิดีโอแรกอาจเป็นแนวทางปฏิบัติที่ดี [ เริ่มช้าสำหรับ AVAudioPlayer ในครั้งแรกที่เล่นเสียง

ฉันอยากจะลดเวลาเริ่มต้นของวิดีโอให้มากที่สุดและมีแนวคิดบางอย่างที่จะทดลอง แต่ต้องการคำแนะนำจากใครก็ได้ที่อาจช่วยได้

อัปเดต: แนวคิดที่ 7 ด้านล่างเมื่อนำไปใช้จะให้เวลาในการเปลี่ยนประมาณ 500 มิลลิวินาที นี่เป็นการปรับปรุง แต่ก็เป็นการดีที่จะทำให้เร็วขึ้นกว่านี้

แนวคิดที่ 1: ใช้ N AVPlayers (ใช้ไม่ได้)

ใช้อAVPPlayerอบเจ็กต์~ 10 รายการและเริ่มและหยุดคลิปทั้งหมด ~ 10 คลิปและเมื่อเรารู้ว่าอันไหนที่เราต้องการจริงๆให้เปลี่ยนไปใช้และยกเลิกการหยุดชั่วคราวที่ถูกต้องAVPlayerและเริ่มต้นใหม่อีกครั้งสำหรับรอบถัดไป

ฉันไม่คิดว่าจะได้ผลเพราะฉันอ่านมาแล้วว่าAVPlayer'siOS มีขีด จำกัด 4 อย่าง มีคนถามเกี่ยวกับเรื่องนี้ใน StackOverflow ที่นี่และพบเกี่ยวกับขีด จำกัด 4 AVPlayer: การสลับอย่างรวดเร็วระหว่างวิดีโอโดยใช้ avfoundation

แนวคิด 2: ใช้ AVQueuePlayer (ใช้ไม่ได้)

ฉันไม่เชื่อว่าการยัด 10 AVPlayerItemsลงไปAVQueuePlayerจะโหลดไว้ล่วงหน้าทั้งหมดเพื่อการเริ่มต้นที่ราบรื่น AVQueuePlayerเป็นคิวและฉันคิดว่ามันทำให้วิดีโอถัดไปในคิวพร้อมสำหรับการเล่นทันที ฉันไม่รู้ว่าเราอยากจะเล่นวิดีโอไหนใน ~ 10 เรื่องจนกว่าจะถึงเวลาเริ่มเล่น iOS-AVPlayer-วิดีโอพรีโหลด

แนวคิด 3: โหลดเล่นและเก็บไว้AVPlayerItemsในพื้นหลัง (ยังไม่แน่ใจ 100% - แต่ดูไม่ดี)

ฉันกำลังดูว่าจะมีประโยชน์อะไรในการโหลดและเล่นวินาทีแรกของแต่ละคลิปวิดีโอในพื้นหลัง (หยุดการแสดงผลวิดีโอและเสียง) และให้อ้างอิงถึงแต่ละรายการAVPlayerItemและเมื่อเรารู้ว่าจะต้องเล่นรายการใด จริงสลับอันนั้นและสลับ AVPlayer พื้นหลังกับอันที่ใช้งานอยู่ ล้างและทำซ้ำ

ทฤษฏีคือผู้เล่นที่เพิ่งเล่นAVPlayer/AVPlayerItemอาจยังคงมีทรัพยากรที่เตรียมไว้ซึ่งจะทำให้การเล่นครั้งต่อ ๆ ไปเร็วขึ้น จนถึงตอนนี้ฉันยังไม่เห็นประโยชน์จากสิ่งนี้ แต่ฉันอาจAVPlayerLayerตั้งค่าพื้นหลังไม่ถูกต้อง ฉันสงสัยว่านี่จะช่วยปรับปรุงสิ่งต่างๆจากที่ฉันเคยเห็น

แนวคิด 4: ใช้รูปแบบไฟล์อื่น - อาจจะโหลดเร็วกว่า

ฉันกำลังใช้รูปแบบ H.264 ของ. m4v (video-MPEG4) H.264 มีตัวเลือกตัวแปลงสัญญาณที่แตกต่างกันมากมายดังนั้นจึงเป็นไปได้ว่าบางตัวเลือกจะค้นหาได้เร็วกว่าตัวเลือกอื่น ๆ ฉันพบว่าการใช้การตั้งค่าขั้นสูงที่ทำให้ขนาดไฟล์เล็กลงจะเพิ่มเวลาในการค้นหา แต่ไม่พบตัวเลือกใด ๆ ที่ไปในทางอื่น

แนวคิดที่ 5: การผสมผสานระหว่างรูปแบบวิดีโอแบบไม่สูญเสียข้อมูล + AVQueuePlayer

หากมีรูปแบบวิดีโอที่โหลดเร็ว แต่อาจจะเป็นที่ที่ขนาดไฟล์เสียไปแนวคิดหนึ่งก็คือเตรียม 10 วินาทีแรกของคลิปวิดีโอแต่ละคลิปไว้ล่วงหน้าด้วยเวอร์ชันที่ป่อง แต่โหลดเร็วขึ้น แต่กลับ ที่ขึ้นกับเนื้อหาที่เข้ารหัสใน H.264 ใช้ AVQueuePlayer และเพิ่ม 10 วินาทีแรกในรูปแบบไฟล์ที่ไม่มีการบีบอัดและตามด้วยไฟล์ที่อยู่ใน H.264 ซึ่งจะใช้เวลาเตรียม / โหลดล่วงหน้าสูงสุด 10 วินาที ดังนั้นฉันจะได้รับ 'สิ่งที่ดีที่สุด' จากทั้งสองโลก: เวลาเริ่มต้นที่รวดเร็ว แต่ยังได้รับประโยชน์จากรูปแบบที่กะทัดรัดกว่าด้วย

แนวคิดที่ 6: ใช้ AVPlayer ที่ไม่ได้มาตรฐาน / เขียนของฉันเอง / ใช้ของคนอื่น

ด้วยความต้องการของฉันฉันอาจไม่สามารถใช้ AVPlayer ได้ แต่ต้องหันไปใช้ AVAssetReader และถอดรหัสในสองสามวินาทีแรก (อาจเขียนไฟล์ดิบลงดิสก์) และเมื่อพูดถึงการเล่นให้ใช้รูปแบบดิบเพื่อเล่น กลับเร็ว ดูเหมือนจะเป็นโครงการใหญ่สำหรับฉันและถ้าฉันทำแบบไร้เดียงสามันก็ไม่ชัดเจน / ไม่น่าจะทำได้ดีกว่านี้ เฟรมวิดีโอที่ถอดรหัสและไม่บีบอัดแต่ละเฟรมมีขนาด 2.25 MB พูดอย่างไร้เดียงสา - ถ้าเราใช้ ~ 30 fps สำหรับวิดีโอฉันจะจบลงด้วยข้อกำหนดการอ่านจากดิสก์ ~ 60 MB / s ซึ่งอาจเป็นไปไม่ได้ / ผลักดันมัน เห็นได้ชัดว่าเราต้องทำการบีบอัดภาพในระดับหนึ่ง (อาจเป็นรูปแบบการบีบอัด openGL / es เนทีฟผ่าน PVRTC) ... อาจจะมีห้องสมุดที่ฉันสามารถใช้?

แนวคิด 7: รวมทุกอย่างไว้ในเนื้อหาภาพยนตร์เรื่องเดียวและ askToTime

แนวคิดหนึ่งที่อาจง่ายกว่าข้อใดข้อหนึ่งคือการรวมทุกอย่างไว้ในภาพยนตร์เรื่องเดียวและใช้ askToTime สิ่งนี้คือเราจะกระโดดไปรอบ ๆ สถานที่ เข้าถึงภาพยนตร์โดยสุ่มเป็นหลัก ฉันคิดว่าสิ่งนี้อาจใช้งานได้จริง: avplayer-movie-playing-lag-in-ios5

แนวทางใดที่คุณคิดว่าดีที่สุด จนถึงตอนนี้ฉันยังไม่มีความคืบหน้ามากนักในแง่ของการลดความล่าช้า


สำหรับสิ่งที่คุ้มค่าฉันจะไปกับ Idea 7 มันยังช้า แต่ก็ไม่ช้าอย่างที่คาดเดาไม่ได้เหมือนกับตัวเลือกอื่น ๆ คำถามต่อไปที่ฉันมีคือ - ตัวเลือกตัวแปลงสัญญาณความละเอียดและความถี่ของคีย์เฟรมมีผลต่อเวลาในการค้นหาหรือไม่
Bernt Habermeier

เล่นเกมช้า แต่อาจคุ้มค่าที่จะเปลี่ยนวิดีโอให้เร็วที่สุดเท่าที่จะเป็นไปได้ (เช่นทันทีหลังจากที่วิดีโอใหม่เริ่มเล่น) และสร้างโปรไฟล์แอปเพื่อดูว่าใช้เวลาส่วนใหญ่ของ CPU ไปที่ใด
tc.

1
เกือบหนึ่งปีต่อมาคุณเคยรู้อะไรเกี่ยวกับเรื่องนี้บ้าง?
lnafziger

1
ฉันไปกับตัวเลือก 7 และไปที่ใดก็ได้ระหว่าง 300ms ถึง 500ms แสวงหา สิ่งหนึ่งที่ฉันพบคือผู้ที่ชื่นชอบตัวเลือกตัวแปลงสัญญาณ mp4 ยิ่งทำให้การค้นหาช้าลง มีตัวเลือกการบีบอัดวิดีโอบางตัวที่ช่วยให้สามารถบีบอัดและรักษาคุณภาพของวิดีโอได้ดีขึ้น แต่ช่วยลดเวลาในการถอดรหัส
Bernt Habermeier

2
นั่นเป็นคำขอที่ฟังดูสมเหตุสมผล แต่ในการนำอ็อพชัน 7 ไปใช้จริงๆมีหลายแง่มุมในการใช้งานที่จะโพสต์ พิจารณา: (ก) สร้างห่วงโซ่เครื่องมือเพื่อรวมเนื้อหาวิดีโอ (b) ตรวจสอบให้แน่ใจว่าคุณติดตามการชดเชยส่วนของวิดีโอ (c) ออฟเซ็ตการค้นหาเมื่อมีคำขอเข้ามาเพื่อเล่นคลิปเฉพาะ (d) ใช้ addPeriodicTimeObserverForInterval เพื่อตรวจสอบ หากคุณวิ่งออกจากคลิปวิดีโอและตอบสนองตามนั้น (ใช้วิธีนี้เทียบกับ addBoundaryTimeObserverForTimes ครั้งเดียวเพราะฉันพบว่าบางครั้งไม่ทริกเกอร์ ... โดยรวมแล้วสิ่งนี้ไม่ได้ให้ยืมตัวเองในการวางโค้ด
Bernt Habermeier

คำตอบ:


4

สำหรับ iOS 10.x ขึ้นไปเพื่อลดความล่าช้าในการเริ่ม AVPlayer ที่ฉันตั้งไว้: avplayer.automaticallyWaitsToMinimizeStalling = false; และดูเหมือนว่าจะแก้ไขได้สำหรับฉัน สิ่งนี้อาจมีผลตามมาอื่น ๆ แต่ฉันยังไม่ถึงจุดนั้น

ฉันได้แนวคิดมาจาก: https://stackoverflow.com/a/50598525/9620547


ผมหน่วงเวลาลดลง 6-7 วินาที แต่ฉันมีคำถามที่นี่มันจะส่งผลต่อประสิทธิภาพของแอปหรือไม่
kalpa

@kalpa เราใช้รหัสนี้ในแอปที่ใช้งานจริงมานานกว่าหนึ่งปีโดยไม่มีผลกระทบเชิงลบใด ๆ ส่วนใหญ่เราเล่นไฟล์เสียง 20-60 นาทีให้กับผู้ฟังในสหรัฐอเมริกาซึ่งโดยทั่วไปแล้วการครอบคลุมข้อมูลมือถือจะรวดเร็ว กรณีการใช้งานของคุณอาจแตกต่างออกไป
grizzb

ขอบคุณสำหรับข้อมูล. @grizzb
kalpa

1

เนื้อหาอาจไม่พร้อมเมื่อคุณสร้างมันอาจทำการคำนวณเช่นระยะเวลาของภาพยนตร์ตรวจสอบให้แน่ใจว่ามีข้อมูลเมตาทั้งหมดของภาพยนตร์ในไฟล์


1

คุณควรลองใช้ตัวเลือก # 7 ก่อนเพื่อดูว่าคุณสามารถใช้งานได้หรือไม่ ฉันสงสัยว่ามันจะไม่ได้ผลจริงสำหรับความต้องการของคุณเนื่องจากเวลาในการค้นหาอาจไม่เร็วพอที่จะให้คุณสลับระหว่างคลิปได้อย่างราบรื่น หากคุณลองแล้วล้มเหลวฉันขอแนะนำให้คุณทำตัวเลือก 4/6 และดูไลบรารี iOS ของฉันที่ออกแบบมาเพื่อจุดประสงค์นี้โดยเฉพาะเพียงแค่ทำการค้นหาโดย Google อย่างรวดเร็วบน AVAnimator เพื่อดูข้อมูลเพิ่มเติม ไลบรารีของฉันทำให้สามารถใช้ลูปแบบไร้รอยต่อและเปลี่ยนจากคลิปหนึ่งไปเป็นอีกคลิปหนึ่งได้มันเร็วมากเพราะต้องถอดรหัสวิดีโอเป็นไฟล์ก่อนถึงมือ ในกรณีของคุณคลิปวิดีโอทั้ง 10 คลิปจะถูกถอดรหัสเป็นไฟล์ก่อนที่คุณจะเริ่ม แต่การสลับไปมาจะทำได้อย่างรวดเร็ว


แล้วส่วนเสียงของวิดีโอล่ะ? ฉันต้องการให้วิดีโอและเสียงตรงกัน
Bernt Habermeier

ใช่เสียงได้รับการจัดการแล้วด้วยการซิงค์ที่แน่นมากระหว่างแทร็กเสียงและคลิปวิดีโอ ดูตัวอย่างโครงการ xcode มันถูกนำไปใช้แล้วทั้งหมดคุณเพียงแค่ต้องดาวน์โหลดและทดลองใช้
MoDJ

มีความเป็นไปได้ที่จะเล่นวิดีโอเครือข่ายโดยใช้ AVAnimator หรือไม่?
Richard Topchii

ไม่มันใช้งานได้กับไฟล์ในเครื่องการสตรีมวิดีโอบนเครือข่ายเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง
MoDJ

0

โดยไม่เคยทำอะไรแบบนี้มาก่อนจากความคิดและประสบการณ์ของคุณฉันจะลองผสมผสานระหว่าง 7 และ 1: โหลด AVPlayer หนึ่งรายการล่วงหน้าด้วยสองสามวินาทีแรกของวิดีโอติดตาม 10 รายการ จากนั้นการข้ามจะรวดเร็วและเชื่อถือได้มากขึ้นเนื่องจากข้อมูลน้อยลง ในขณะที่คุณกำลังเล่นชิ้นส่วนที่เลือกคุณมีเวลาเพียงพอในการเตรียม AVPlayer สำหรับวิดีโอติดตามผลที่เหลือที่เลือกไว้ในพื้นหลัง เมื่อการเริ่มต้นเสร็จสิ้นคุณเปลี่ยนไปใช้ AVPlayer ที่เตรียมไว้ ดังนั้นโดยรวมแล้วคุณสามารถโหลด AVPlayers ได้สูงสุด 2 เครื่องในช่วงเวลาใดเวลาหนึ่ง

แน่นอนว่าฉันไม่รู้ว่าการสลับสามารถทำได้อย่างราบรื่นจนไม่รบกวนการเล่นหรือไม่

(จะเพิ่มสิ่งนี้เป็นความคิดเห็นถ้าฉันทำได้)

ดีที่สุดปีเตอร์


ฉันไม่พบว่ามีประโยชน์ในการโหลดเนื้อหา 10 รายการในหนึ่งชุดบน AVPlayer นอกจากนี้ฉันไม่เข้าใจคำแนะนำของคุณเนื่องจากฉันเห็นตัวเลือก (1) และ (7) ที่ไม่สามารถใช้ร่วมกันได้ ตัวเลือกที่ 7 จะรวมเนื้อหาวิดีโอทั้งหมดไว้ในเนื้อหาเดียวดังนั้นจึงมีช่วงเวลาโหลดเนื้อหาเดียวเท่านั้น นี่คือสิ่งที่ฉันทำในวันนี้และสำหรับสิ่งที่คุ้มค่าฉันได้รับความล่าช้าประมาณ 500 มิลลิวินาทีในเวลาเริ่มต้น / เล่นจริง เป็นที่น่าสังเกตว่า SeekTo เล่นเสร็จเร็วกว่าเฟรมแรกที่เล่นจริงดังนั้นสำหรับเวลาเริ่มต้นที่แท้จริงฉันวัดเมื่อเฟรมแรกเล่นจริงผ่านการโทรกลับตามกำหนดเวลา
Bernt Habermeier

เพียงเพื่อให้ความคิดของฉันชัดเจน: ความคิดของฉันคือการแบ่งเนื้อหาออกเป็นสองส่วน: สองสามวินาทีแรกและส่วนที่เหลือ ตอนนี้คุณได้สร้างสองสินทรัพย์จากหนึ่งรายการ 10 จุดเริ่มต้นที่คุณเข้าร่วมและใช้การข้ามและในขณะที่เล่นจุดเริ่มต้นคุณจะโหลดส่วนที่เหลือ จากนั้นจะประกอบด้วยข้อเสนอแนะ 8 ซึ่งเพิ่มในรายการของคุณ
ilmiacs

แต่ตามที่ฉันเข้าใจจากความคิดเห็นล่าสุดของคุณในขณะที่คุณผลักดันการวิจัยเพิ่มเติมซึ่งเป็นสิ่งที่ดีและโซลูชัน 7 ก็ไม่ได้ผลเช่นกัน ดูเหมือนว่าโดยพื้นฐานแล้ว AV ยังคงอยู่ในทางของคุณและวิธีเดียวที่คุณจะไปได้ก็คือการใช้เทคโนโลยีที่อยู่ข้างใต้นั่นคือ Core Media เพื่อให้สามารถควบคุมทรัพย์สินของคุณได้มากขึ้น จางไป
ilmiacs

โอ้ฉันเข้าใจความคิดของคุณดีขึ้นแล้ว ขอบคุณ เป็นเรื่องน่าสนใจที่จะตรวจสอบว่าคุณมีเวลาค้นหาคลิปวิดีโอสั้น ๆ อย่างรวดเร็วหรือไม่ ฉันยังไม่ได้ลอง แต่ควรค่าแก่การพิจารณา เกี่ยวกับการใช้สื่อหลัก - ฉันไม่พบข้อมูลอ้างอิงที่ดีใน API นั้น คุณมีแหล่งข้อมูลที่ดีที่สามารถชี้ให้ฉันทราบได้หรือไม่?
Bernt Habermeier

ไม่ขอโทษ อย่างที่บอกว่าฉันไม่ใช่ผู้เชี่ยวชาญเรื่อง AV หรือ Core Media เพียงอ่านคำถามของคุณและมีความคิดว่าฉันจะดำเนินการอย่างไรเป็นการส่วนตัวและต้องการแบ่งปัน Peter
ilmiacs

0

หากฉันเข้าใจปัญหาของคุณอย่างถูกต้องดูเหมือนว่าคุณจะมีวิดีโอต่อเนื่องหนึ่งรายการซึ่งคุณต้องโหลดแทร็กเสียงเพื่อแจ้งให้ทราบล่วงหน้า

หากเป็นกรณีที่ผมขอแนะนำให้มองเข้าไปในBASS BASS เป็นไลบรารีเสียงเหมือนกับ AVPlayer ที่ให้คุณ (ค่อนข้าง) เข้าถึง API ระดับต่ำของเฟรมเวิร์ก AudioUnits ใน iOS มีความหมายอย่างไรสำหรับคุณ? หมายความว่าด้วยการจัดการบัฟเฟอร์เพียงเล็กน้อย (คุณอาจไม่จำเป็นต้องใช้มันขึ้นอยู่กับว่าคุณต้องการดีเลย์แค่ไหน) คุณสามารถเริ่มเล่นเพลงได้ทันที

อย่างไรก็ตามข้อ จำกัด นั้นขยายไปถึงวิดีโออย่างที่ฉันพูดไปมันเป็นไลบรารีเสียงดังนั้นการปรับแต่งวิดีโอใด ๆ ก็ยังคงต้องทำด้วย AVPlayer อย่างไรก็ตามการใช้-seekToTime:toleranfeBefore:toleranceAfter:คุณควรจะสามารถค้นหาได้อย่างรวดเร็วภายในวิดีโอตราบเท่าที่คุณลงทะเบียนล่วงหน้าด้วยตัวเลือกที่จำเป็นทั้งหมด

หากคุณกำลังซิงค์กับอุปกรณ์หลายเครื่อง (ซึ่งแอปพลิเคชันของคุณอาจแนะนำ) เพียงแค่แสดงความคิดเห็นและฉันยินดีที่จะแก้ไขคำตอบของฉัน

PS: BASS อาจดูน่ากลัวในตอนแรกเนื่องจากเป็นรูปแบบ C-like แต่มันง่ายมากที่จะใช้กับสิ่งที่เป็นอยู่


-2

นี่คือคุณสมบัติและวิธีการต่างๆที่จัดเตรียมโดยคลาส AVAsset ซึ่งอาจช่วยได้:

- (void)_pu_setCachedDuration:(id)arg1;
- (id)pu_cachedDuration; 
- (struct
 { 
   long long x1; 
   int x2;
   unsigned int x3; 
   long long x4; 
})pu_duration;
- (void)pu_loadDurationWithCompletionHandler:(id /* block */)arg1;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.