Compare commits

..

4 Commits

Author SHA1 Message Date
Romain Vimont
d6aaa5bf9a Add a FAQ section for Wayland support
The video driver might need to be explicitly set to wayland.

Refs #2554 <https://github.com/Genymobile/scrcpy/issues/2554>
Refs #2559 <https://github.com/Genymobile/scrcpy/issues/2559>
2021-08-13 12:40:22 +02:00
kosantosbik
4ab3e89c29 Add README file in Turkish
PR #2514 <https://github.com/Genymobile/scrcpy/pull/2514>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:42:31 +02:00
Hyperterminal Byte
4bc78244b9 Fix OBS project ref URL
PR #2545 <https://github.com/Genymobile/scrcpy/pull/2545>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:29:17 +02:00
Stefan Huber
ea233d811d Fix typo in DEVELOP.md
PR #2540 <https://github.com/Genymobile/scrcpy/pull/2540>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:26:32 +02:00
24 changed files with 1042 additions and 705 deletions

View File

@@ -76,7 +76,7 @@ The server uses 3 threads:
- the **main** thread, encoding and streaming the video to the client; - the **main** thread, encoding and streaming the video to the client;
- the **controller** thread, listening for _control messages_ (typically, - the **controller** thread, listening for _control messages_ (typically,
keyboard and mouse events) from the client; keyboard and mouse events) from the client;
- the **receiver** thread (managed by the controller), sending _device messges_ - the **receiver** thread (managed by the controller), sending _device messages_
to the clients (currently, it is only used to send the device clipboard to the clients (currently, it is only used to send the device clipboard
content). content).

20
FAQ.md
View File

@@ -153,6 +153,26 @@ You may also need to configure the [scaling behavior]:
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### Issue with Wayland
By default, SDL uses x11 on Linux. The [video driver] can be changed via the
`SDL_VIDEODRIVER` environment variable:
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
On some distributions (at least Fedora), the package `libdecor` must be
installed manually.
See issues [#2554] and [#2559].
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### KWin compositor crashes ### KWin compositor crashes

View File

@@ -318,7 +318,7 @@ vlc v4l2:///dev/videoN # VLC might add some buffering delay
For example, you could capture the video within [OBS]. For example, you could capture the video within [OBS].
[OBS]: https://obsproject.com/fr [OBS]: https://obsproject.com/
### Connection ### Connection
@@ -875,5 +875,6 @@ This README is available in other languages:
- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)
Only this README file is guaranteed to be up-to-date. Only this README file is guaranteed to be up-to-date.

824
README.tr.md Normal file
View File

@@ -0,0 +1,824 @@
# scrcpy (v1.18)
Bu uygulama Android cihazların USB (ya da [TCP/IP][article-tcpip]) üzerinden
görüntülenmesini ve kontrol edilmesini sağlar. _root_ erişimine ihtiyaç duymaz.
_GNU/Linux_, _Windows_ ve _macOS_ sistemlerinde çalışabilir.
![screenshot](assets/screenshot-debian-600.jpg)
Öne çıkan özellikler:
- **hafiflik** (doğal, sadece cihazın ekranını gösterir)
- **performans** (30~60fps)
- **kalite** (1920×1080 ya da üzeri)
- **düşük gecikme süresi** ([35~70ms][lowlatency])
- **düşük başlangıç süresi** (~1 saniye ilk kareyi gösterme süresi)
- **müdaheleci olmama** (cihazda kurulu yazılım kalmaz)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Gereksinimler
Android cihaz en düşük API 21 (Android 5.0) olmalıdır.
[Adb hata ayıklamasının][enable-adb] cihazınızda aktif olduğundan emin olun.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Bazı cihazlarda klavye ve fare ile kontrol için [ilave bir seçenek][control] daha
etkinleştirmeniz gerekebilir.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Uygulamayı indirin
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Özet
- Linux: `apt install scrcpy`
- Windows: [indir][direct-win64]
- macOS: `brew install scrcpy`
Kaynak kodu derle: [BUILD] ([basitleştirilmiş süreç][build_simple])
[build]: BUILD.md
[build_simple]: BUILD.md#simple
### Linux
Debian (şimdilik _testing_ ve _sid_) ve Ubuntu (20.04) için:
```
apt install scrcpy
```
[Snap] paketi: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Fedora için, [COPR] paketi: [`scrcpy`][copr-link].
[copr]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Arch Linux için, [AUR] paketi: [`scrcpy`][aur-link].
[aur]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Gentoo için, [Ebuild] mevcut: [`scrcpy/`][ebuild-link].
[ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build] ([basitleştirilmiş süreç][build_simple]).
### Windows
Windows için (`adb` dahil) tüm gereksinimleri ile derlenmiş bir arşiv mevcut:
- [README](README.md#windows)
[Chocolatey] ile kurulum:
[chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # if you don't have it yet
```
[Scoop] ile kurulum:
```bash
scoop install scrcpy
scoop install adb # if you don't have it yet
```
[scoop]: https://scoop.sh
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build].
### macOS
Uygulama [Homebrew] içerisinde mevcut. Sadece kurun:
[homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`adb`, `PATH` içerisinden erişilebilir olmalıdır. Eğer değilse:
```bash
brew install android-platform-tools
```
[MacPorts] kullanılarak adb ve uygulamanın birlikte kurulumu yapılabilir:
```bash
sudo port install scrcpy
```
[macports]: https://www.macports.org/
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build].
## Çalıştırma
Android cihazınızı bağlayın ve aşağıdaki komutu çalıştırın:
```bash
scrcpy
```
Komut satırı argümanları aşağıdaki komut ile listelenebilir:
```bash
scrcpy --help
```
## Özellikler
### Ekran yakalama ayarları
#### Boyut azaltma
Bazen, Android cihaz ekranını daha düşük seviyede göstermek performansı artırabilir.
Hem genişliği hem de yüksekliği bir değere sabitlemek için (ör. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # kısa versiyon
```
Diğer boyut en-boy oranı korunacak şekilde hesaplanır.
Bu şekilde ekran boyutu 1920x1080 olan bir cihaz 1024x576 olarak görünür.
#### Bit-oranı değiştirme
Varsayılan bit-oranı 8 Mbps'dir. Değiştirmek için (ör. 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # kısa versiyon
```
#### Çerçeve oranı sınırlama
Ekran yakalama için maksimum çerçeve oranı için sınır koyulabilir:
```bash
scrcpy --max-fps 15
```
Bu özellik Android 10 ve sonrası sürümlerde resmi olarak desteklenmektedir,
ancak daha önceki sürümlerde çalışmayabilir.
#### Kesme
Cihaz ekranının sadece bir kısmı görünecek şekilde kesilebilir.
Bu özellik Oculus Go'nun bir gözünü yakalamak gibi durumlarda kullanışlı olur:
```bash
scrcpy --crop 1224:1440:0:0 # (0,0) noktasından 1224x1440
```
Eğer `--max-size` belirtilmişse yeniden boyutlandırma kesme işleminden sonra yapılır.
#### Video yönünü kilitleme
Videonun yönünü kilitlemek için:
```bash
scrcpy --lock-video-orientation # başlangıç yönü
scrcpy --lock-video-orientation=0 # doğal yön
scrcpy --lock-video-orientation=1 # 90° saatin tersi yönü
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° saat yönü
```
Bu özellik kaydetme yönünü de etkiler.
[Pencere ayrı olarak döndürülmüş](#rotation) olabilir.
#### Kodlayıcı
Bazı cihazlar birden fazla kodlayıcıya sahiptir, ve bunların bazıları programın
kapanmasına sebep olabilir. Bu durumda farklı bir kodlayıcı seçilebilir:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Mevcut kodlayıcıları listelemek için geçerli olmayan bir kodlayıcı ismi girebilirsiniz,
hata mesajı mevcut kodlayıcıları listeleyecektir:
```bash
scrcpy --encoder _
```
### Yakalama
#### Kaydetme
Ekran yakalama sırasında kaydedilebilir:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Yakalama olmadan kayıt için:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+C ile kayıt kesilebilir
```
"Atlanan kareler" gerçek zamanlı olarak gösterilmese (performans sebeplerinden ötürü) dahi kaydedilir.
Kareler cihazda _zamandamgası_ ile saklanır, bu sayede [paket gecikme varyasyonu]
kayıt edilen dosyayı etkilemez.
[paket gecikme varyasyonu]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Linux'ta video akışı bir v4l2 loopback cihazına gönderilebilir. Bu sayede Android
cihaz bir web kamerası gibi davranabilir.
Bu işlem için `v4l2loopback` modülü kurulu olmalıdır:
```bash
sudo apt install v4l2loopback-dkms
```
v4l2 cihazı oluşturmak için:
```bash
sudo modprobe v4l2loopback
```
Bu komut `/dev/videoN` adresinde `N` yerine bir tamsayı koyarak yeni bir video
cihazı oluşturacaktır.
(birden fazla cihaz oluşturmak veya spesifik ID'ye sahip cihazlar için
diğer [seçenekleri](https://github.com/umlaeute/v4l2loopback#options) inceleyebilirsiniz.)
Aktif cihazları listelemek için:
```bash
# v4l-utils paketi ile
v4l2-ctl --list-devices
# daha basit ama yeterli olabilecek şekilde
ls /dev/video*
```
v4l2 kullanarak scrpy kullanmaya başlamak için:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # ayna penceresini kapatarak
scrcpy --v4l2-sink=/dev/videoN -N # kısa versiyon
```
(`N` harfini oluşturulan cihaz ID numarası ile değiştirin. `ls /dev/video*` cihaz ID'lerini görebilirsiniz.)
Aktifleştirildikten sonra video akışını herhangi bir v4l2 özellikli araçla açabilirsiniz:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC kullanırken yükleme gecikmesi olabilir
```
Örneğin, [OBS] ile video akışını kullanabilirsiniz.
[obs]: https://obsproject.com/
### Bağlantı
#### Kablosuz
_Scrcpy_ cihazla iletişim kurmak için `adb`'yi kullanır, Ve `adb`
bir cihaza TCP/IP kullanarak [bağlanabilir].
1. Cihazınızı bilgisayarınızla aynı Wi-Fi ağına bağlayın.
2. Cihazınızın IP adresini bulun. Ayarlar → Telefon Hakkında → Durum sekmesinden veya
aşağıdaki komutu çalıştırarak öğrenebilirsiniz:
```bash
adb shell ip route | awk '{print $9}'
```
3. Cihazınızda TCP/IP üzerinden adb kullanımını etkinleştirin: `adb tcpip 5555`.
4. Cihazınızı bilgisayarınızdan sökün.
5. Cihazınıza bağlanın: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` değerini değiştirin)_.
6. `scrcpy` komutunu normal olarak çalıştırın.
Bit-oranını ve büyüklüğü azaltmak yararlı olabilir:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # kısa version
```
[bağlanabilir]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Birden fazla cihaz
Eğer `adb devices` komutu birden fazla cihaz listeliyorsa _serial_ değerini belirtmeniz gerekir:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # kısa versiyon
```
Eğer cihaz TCP/IP üzerinden bağlanmışsa:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # kısa version
```
Birden fazla cihaz için birden fazla _scrcpy_ uygulaması çalıştırabilirsiniz.
#### Cihaz bağlantısı ile otomatik başlatma
[AutoAdb] ile yapılabilir:
```bash
autoadb scrcpy -s '{}'
```
[autoadb]: https://github.com/rom1v/autoadb
#### SSH Tünel
Uzaktaki bir cihaza erişmek için lokal `adb` istemcisi, uzaktaki bir `adb` sunucusuna
(aynı _adb_ sürümünü kullanmak şartı ile) bağlanabilir :
```bash
adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# bunu açık tutun
```
Başka bir terminalde:
```bash
scrcpy
```
Uzaktan port yönlendirme ileri yönlü bağlantı kullanabilirsiniz
(`-R` yerine `-L` olduğuna dikkat edin):
```bash
adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# bunu açık tutun
```
Başka bir terminalde:
```bash
scrcpy --force-adb-forward
```
Kablosuz bağlantı gibi burada da kalite düşürmek faydalı olabilir:
```
scrcpy -b2M -m800 --max-fps 15
```
### Pencere ayarları
#### İsim
Cihaz modeli varsayılan pencere ismidir. Değiştirmek için:
```bash
scrcpy --window-title 'Benim cihazım'
```
#### Konum ve
Pencerenin başlangıç konumu ve boyutu belirtilebilir:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Kenarlıklar
Pencere dekorasyonunu kapatmak için:
```bash
scrcpy --window-borderless
```
#### Her zaman üstte
Scrcpy penceresini her zaman üstte tutmak için:
```bash
scrcpy --always-on-top
```
#### Tam ekran
Uygulamayı tam ekran başlatmak için:
```bash
scrcpy --fullscreen
scrcpy -f # kısa versiyon
```
Tam ekran <kbd>MOD</kbd>+<kbd>f</kbd> ile dinamik olarak değiştirilebilir.
#### Döndürme
Pencere döndürülebilir:
```bash
scrcpy --rotation 1
```
Seçilebilecek değerler:
- `0`: döndürme yok
- `1`: 90 derece saat yönünün tersi
- `2`: 180 derece
- `3`: 90 derece saat yönü
Döndürme <kbd>MOD</kbd>+<kbd>←</kbd>_(sol)_ ve
<kbd>MOD</kbd>+<kbd>→</kbd> _(sağ)_ ile dinamik olarak değiştirilebilir.
_scrcpy_'de 3 farklı döndürme olduğuna dikkat edin:
- <kbd>MOD</kbd>+<kbd>r</kbd> cihazın yatay veya dikey modda çalışmasını sağlar.
(çalışan uygulama istenilen oryantasyonda çalışmayı desteklemiyorsa döndürme
işlemini reddedebilir.)
- [`--lock-video-orientation`](#lock-video-orientation) görüntü yakalama oryantasyonunu
(cihazdan bilgisayara gelen video akışının oryantasyonu) değiştirir. Bu kayıt işlemini
etkiler.
- `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
pencere içeriğini dönderir. Bu sadece canlı görüntüyü etkiler, kayıt işlemini etkilemez.
### Diğer ekran yakalama seçenekleri
#### Yazma korumalı
Kontrolleri devre dışı bırakmak için (cihazla etkileşime geçebilecek her şey: klavye ve
fare girdileri, dosya sürükleyip bırakma):
```bash
scrcpy --no-control
scrcpy -n
```
#### Ekran
Eğer cihazın birden fazla ekranı varsa hangi ekranın kullanılacağını seçebilirsiniz:
```bash
scrcpy --display 1
```
Kullanılabilecek ekranları listelemek için:
```bash
adb shell dumpsys display # çıktı içerisinde "mDisplayId=" terimini arayın
```
İkinci ekran ancak cihaz Android sürümü 10 veya üzeri olmalıdır (değilse yazma korumalı
olarak görüntülenir).
#### Uyanık kalma
Cihazın uyku moduna girmesini engellemek için:
```bash
scrcpy --stay-awake
scrcpy -w
```
scrcpy kapandığında cihaz başlangıç durumuna geri döner.
#### Ekranı kapatma
Ekran yakalama sırasında cihazın ekranı kapatılabilir:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Ya da <kbd>MOD</kbd>+<kbd>o</kbd> kısayolunu kullanabilirsiniz.
Tekrar açmak için ise <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> tuşlarına basın.
Android'de, `GÜÇ` tuşu her zaman ekranı açar. Eğer `GÜÇ` sinyali scrcpy ile
gönderilsiyse (sağ tık veya <kbd>MOD</kbd>+<kbd>p</kbd>), ekran kısa bir gecikme
ile kapanacaktır. Fiziksel `GÜÇ` tuşuna basmak hala ekranın açılmasına sebep olacaktır.
Bu cihazın uykuya geçmesini engellemek için kullanılabilir:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Dokunuşları gösterme
Sunumlar sırasında fiziksel dokunuşları (fiziksel cihazdaki) göstermek
faydalı olabilir.
Android'de bu özellik _Geliştici seçenekleri_ içerisinde bulunur.
_Scrcpy_ bu özelliği çalışırken etkinleştirebilir ve kapanırken eski
haline geri getirebilir:
```bash
scrcpy --show-touches
scrcpy -t
```
Bu opsiyon sadece _fiziksel_ dokunuşları (cihaz ekranındaki) gösterir.
#### Ekran koruyucuyu devre dışı bırakma
Scrcpy varsayılan ayarlarında ekran koruyucuyu devre dışı bırakmaz.
Bırakmak için:
```bash
scrcpy --disable-screensaver
```
### Girdi kontrolü
#### Cihaz ekranını dönderme
<kbd>MOD</kbd>+<kbd>r</kbd> tuşları ile yatay ve dikey modlar arasında
geçiş yapabilirsiniz.
Bu kısayol ancak çalışan uygulama desteklediği takdirde ekranı döndürecektir.
#### Kopyala yapıştır
Ne zaman Android cihazdaki pano değişse bilgisayardaki pano otomatik olarak
senkronize edilir.
Tüm <kbd>Ctrl</kbd> kısayolları cihaza iletilir:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> genelde kopyalar
- <kbd>Ctrl</kbd>+<kbd>x</kbd> genelde keser
- <kbd>Ctrl</kbd>+<kbd>v</kbd> genelde yapıştırır (bilgisayar ve cihaz arasındaki
pano senkronizasyonundan sonra)
Bu kısayollar genelde beklediğiniz gibi çalışır.
Ancak kısayolun gerçekten yaptığı eylemi açık olan uygulama belirler.
Örneğin, _Termux_ <kbd>Ctrl</kbd>+<kbd>c</kbd> ile kopyalama yerine
SIGINT sinyali gönderir, _K-9 Mail_ ise yeni mesaj oluşturur.
Bu tip durumlarda kopyalama, kesme ve yapıştırma için (Android versiyon 7 ve
üstü):
- <kbd>MOD</kbd>+<kbd>c</kbd> `KOPYALA`
- <kbd>MOD</kbd>+<kbd>x</kbd> `KES`
- <kbd>MOD</kbd>+<kbd>v</kbd> `YAPIŞTIR` (bilgisayar ve cihaz arasındaki
pano senkronizasyonundan sonra)
Bunlara ek olarak, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> tuşları
bilgisayar pano içeriğini tuş basma eylemleri şeklinde gönderir. Bu metin
yapıştırmayı desteklemeyen (_Termux_ gibi) uygulamar için kullanışlıdır,
ancak ASCII olmayan içerikleri bozabilir.
**UYARI:** Bilgisayar pano içeriğini cihaza yapıştırmak
(<kbd>Ctrl</kbd>+<kbd>v</kbd> ya da <kbd>MOD</kbd>+<kbd>v</kbd> tuşları ile)
içeriği cihaz panosuna kopyalar. Sonuç olarak, herhangi bir Android uygulaması
içeriğe erişebilir. Hassas içerikler (parolalar gibi) için bu özelliği kullanmaktan
kaçının.
Bazı cihazlar pano değişikleri konusunda beklenilen şekilde çalışmayabilir.
Bu durumlarda `--legacy-paste` argümanı kullanılabilir. Bu sayede
<kbd>Ctrl</kbd>+<kbd>v</kbd> ve <kbd>MOD</kbd>+<kbd>v</kbd> tuşları da
pano içeriğini tuş basma eylemleri şeklinde gönderir
(<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> ile aynı şekilde).
#### İki parmak ile yakınlaştırma
"İki parmak ile yakınlaştırma" için: <kbd>Ctrl</kbd>+_tıkla-ve-sürükle_.
Daha açıklayıcı şekilde, <kbd>Ctrl</kbd> tuşuna sol-tık ile birlikte basılı
tutun. Sol-tık serbest bırakılıncaya kadar yapılan tüm fare hareketleri
ekran içeriğini ekranın merkezini baz alarak dönderir, büyütür veya küçültür
(eğer uygulama destekliyorsa).
Scrcpy ekranın merkezinde bir "sanal parmak" varmış gibi davranır.
#### Metin gönderme tercihi
Metin girilirken ili çeşit [eylem][textevents] gerçekleştirilir:
- _tuş eylemleri_, bir tuşa basıldığı sinyalini verir;
- _metin eylemleri_, bir metin girildiği sinyalini verir.
Varsayılan olarak, harfler tuş eylemleri kullanılarak gönderilir. Bu sayede
klavye oyunlarda beklenilene uygun olarak çalışır (Genelde WASD tuşları).
Ancak bu [bazı problemlere][prefertext] yol açabilir. Eğer bu problemler ile
karşılaşırsanız metin eylemlerini tercih edebilirsiniz:
```bash
scrcpy --prefer-text
```
(Ama bu oyunlardaki klavye davranışlarını bozacaktır)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Tuş tekrarı
Varsayılan olarak, bir tuşa basılı tutmak tuş eylemini tekrarlar. Bu durum
bazı oyunlarda problemlere yol açabilir.
Tuş eylemlerinin tekrarını kapatmak için:
```bash
scrcpy --no-key-repeat
```
#### Sağ-tık ve Orta-tık
Varsayılan olarak, sağ-tık GERİ (ya da GÜÇ açma) eylemlerini, orta-tık ise
ANA EKRAN eylemini tetikler. Bu kısayolları devre dışı bırakmak için:
```bash
scrcpy --forward-all-clicks
```
### Dosya bırakma
#### APK kurulumu
APK kurmak için, bilgisayarınızdaki APK dosyasını (`.apk` ile biten) _scrcpy_
penceresine sürükleyip bırakın.
Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır.
#### Dosyayı cihaza gönderme
Bir dosyayı cihazdaki `/sdcard/Download/` dizinine atmak için, (APK olmayan)
bir dosyayı _scrcpy_ penceresine sürükleyip bırakın.
Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır.
Hedef dizin uygulama başlatılırken değiştirilebilir:
```bash
scrcpy --push-target=/sdcard/Movies/
```
### Ses iletimi
_Scrcpy_ ses iletimi yapmaz. Yerine [sndcpy] kullanabilirsiniz.
Ayrıca bakınız [issue #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Kısayollar
Aşağıdaki listede, <kbd>MOD</kbd> kısayol tamamlayıcısıdır. Varsayılan olarak
(sol) <kbd>Alt</kbd> veya (sol) <kbd>Super</kbd> tuşudur.
Bu tuş `--shortcut-mod` argümanı kullanılarak `lctrl`, `rctrl`,
`lalt`, `ralt`, `lsuper` ve `rsuper` tuşlarından biri ile değiştirilebilir.
Örneğin:
```bash
# Sağ Ctrl kullanmak için
scrcpy --shortcut-mod=rctrl
# Sol Ctrl, Sol Alt veya Sol Super tuşlarından birini kullanmak için
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> tuşu genelde <kbd>Windows</kbd> veya <kbd>Cmd</kbd> tuşudur._
[super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut |
| ------------------------------------------------ | :-------------------------------------------------------- |
| Tam ekran modunu değiştirme | <kbd>MOD</kbd>+<kbd>f</kbd> |
| Ekranı sola çevirme | <kbd>MOD</kbd>+<kbd>←</kbd> _(sol)_ |
| Ekranı sağa çevirme | <kbd>MOD</kbd>+<kbd>→</kbd> _(sağ)_ |
| Pencereyi 1:1 oranına çevirme (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> |
| Penceredeki siyah kenarlıkları kaldırma | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Çift-sol-tık¹_ |
| `ANA EKRAN` tuşu | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Orta-tık_ |
| `GERİ` tuşu | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Sağ-tık²_ |
| `UYGULAMA_DEĞİŞTİR` tuşu | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4.tık³_ |
| `MENÜ` tuşu (ekran kilidini açma) | <kbd>MOD</kbd>+<kbd>m</kbd> |
| `SES_AÇ` tuşu | <kbd>MOD</kbd>+<kbd>↑</kbd> _(yukarı)_ |
| `SES_KIS` tuşu | <kbd>MOD</kbd>+<kbd>↓</kbd> _(aşağı)_ |
| `GÜÇ` tuşu | <kbd>MOD</kbd>+<kbd>p</kbd> |
| Gücü açma | _Sağ-tık²_ |
| Cihaz ekranını kapatma (ekran yakalama durmadan) | <kbd>MOD</kbd>+<kbd>o</kbd> |
| Cihaz ekranını açma | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
| Cihaz ekranını dönderme | <kbd>MOD</kbd>+<kbd>r</kbd> |
| Bildirim panelini genişletme | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5.tık³_ |
| Ayarlar panelini genişletme | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Çift-5.tık³_ |
| Panelleri kapatma | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
| Panoya kopyalama⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> |
| Panoya kesme⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> |
| Panoları senkronize ederek yapıştırma⁴ | <kbd>MOD</kbd>+<kbd>v</kbd> |
| Bilgisayar panosundaki metini girme | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
| FPS sayacını açma/kapatma (terminalde) | <kbd>MOD</kbd>+<kbd>i</kbd> |
| İki parmakla yakınlaştırma | <kbd>Ctrl</kbd>+_tıkla-ve-sürükle_ |
_¹Siyah kenarlıkları silmek için üzerine çift tıklayın._
_²Sağ-tık ekran kapalıysa açar, değilse GERİ sinyali gönderir._
_³4. ve 5. fare tuşları (eğer varsa)._
_⁴Sadece Android 7 ve üzeri versiyonlarda._
Tekrarlı tuşu olan kısayollar tuş bırakılıp tekrar basılarak tekrar çalıştırılır.
Örneğin, "Ayarlar panelini genişletmek" için:
1. <kbd>MOD</kbd> tuşuna basın ve basılı tutun.
2. <kbd>n</kbd> tuşuna iki defa basın.
3. <kbd>MOD</kbd> tuşuna basmayı bırakın.
Tüm <kbd>Ctrl</kbd>+_tuş_ kısayolları cihaza gönderilir. Bu sayede istenilen komut
uygulama tarafından çalıştırılır.
## Özel dizinler
Varsayılandan farklı bir _adb_ programı çalıştırmak için `ADB` ortam değişkenini
ayarlayın:
```bash
ADB=/path/to/adb scrcpy
```
`scrcpy-server` programının dizinini değiştirmek için `SCRCPY_SERVER_PATH`
değişkenini ayarlayın.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Neden _scrcpy_?
Bir meslektaşım [gnirehtet] gibi söylenmesi zor bir isim bulmam için bana meydan okudu.
[`strcpy`] **str**ing kopyalıyor; `scrcpy` **scr**een kopyalıyor.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Nasıl derlenir?
Bakınız [BUILD].
## Yaygın problemler
Bakınız [FAQ](FAQ.md).
## Geliştiriciler
[Geliştiriciler sayfası]nı okuyun.
[geliştiriciler sayfası]: DEVELOP.md
## Lisans
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Makaleler
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

View File

@@ -10,7 +10,6 @@ src = [
'src/event_converter.c', 'src/event_converter.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c', 'src/input_manager.c',
'src/opengl.c', 'src/opengl.c',
'src/receiver.c', 'src/receiver.c',

View File

@@ -1,6 +1,7 @@
#include "fps_counter.h" #include "fps_counter.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h>
#include "util/log.h" #include "util/log.h"
@@ -81,12 +82,14 @@ run_fps_counter(void *data) {
sc_cond_wait(&counter->state_cond, &counter->mutex); sc_cond_wait(&counter->state_cond, &counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
sc_cond_timedwait(&counter->state_cond, &counter->mutex, sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
counter->next_timestamp);
} }
} }
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -96,7 +99,7 @@ run_fps_counter(void *data) {
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL_MS; counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -162,7 +165,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
} }
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -175,7 +178,7 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
} }
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);

View File

@@ -24,7 +24,7 @@ struct fps_counter {
bool interrupted; bool interrupted;
unsigned nr_rendered; unsigned nr_rendered;
unsigned nr_skipped; unsigned nr_skipped;
sc_tick next_timestamp; uint32_t next_timestamp;
}; };
bool bool

View File

@@ -1,88 +0,0 @@
#include "frame_buffer.h"
#include <assert.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/log.h"
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
fb->pending_frame = av_frame_alloc();
if (!fb->pending_frame) {
return false;
}
fb->tmp_frame = av_frame_alloc();
if (!fb->tmp_frame) {
av_frame_free(&fb->pending_frame);
return false;
}
bool ok = sc_mutex_init(&fb->mutex);
if (!ok) {
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
return false;
}
// there is initially no frame, so consider it has already been consumed
fb->pending_frame_consumed = true;
return true;
}
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb) {
sc_mutex_destroy(&fb->mutex);
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
}
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) {
sc_mutex_lock(&fb->mutex);
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame);
av_frame_unref(fb->tmp_frame);
if (previous_frame_skipped) {
*previous_frame_skipped = !fb->pending_frame_consumed;
}
fb->pending_frame_consumed = false;
sc_mutex_unlock(&fb->mutex);
return true;
}
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) {
sc_mutex_lock(&fb->mutex);
assert(!fb->pending_frame_consumed);
fb->pending_frame_consumed = true;
av_frame_move_ref(dst, fb->pending_frame);
// av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
sc_mutex_unlock(&fb->mutex);
}

View File

@@ -1,44 +0,0 @@
#ifndef SC_FRAME_BUFFER_H
#define SC_FRAME_BUFFER_H
#include "common.h"
#include <stdbool.h>
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* A frame buffer holds 1 pending frame, which is the last frame received from
* the producer (typically, the decoder).
*
* If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*/
struct sc_frame_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
sc_mutex mutex;
bool pending_frame_consumed;
};
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb);
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb);
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *skipped);
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst);
#endif

View File

@@ -51,15 +51,16 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(rec->packet);
av_packet_free(&rec->packet); av_packet_free(&rec->packet);
free(rec); free(rec);
} }
static void static void
recorder_queue_clear(struct recorder_queue *queue) { recorder_queue_clear(struct recorder_queue *queue) {
while (!sc_queue_is_empty(queue)) { while (!queue_is_empty(queue)) {
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(queue, next, &rec); queue_take(queue, next, &rec);
record_packet_delete(rec); record_packet_delete(rec);
} }
} }
@@ -135,14 +136,14 @@ run_recorder(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
@@ -161,7 +162,7 @@ run_recorder(void *data) {
} }
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(&recorder->queue, next, &rec); queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
@@ -235,7 +236,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
goto error_mutex_destroy; goto error_mutex_destroy;
} }
sc_queue_init(&recorder->queue); queue_init(&recorder->queue);
recorder->stopped = false; recorder->stopped = false;
recorder->failed = false; recorder->failed = false;
recorder->header_written = false; recorder->header_written = false;
@@ -340,7 +341,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
return false; return false;
} }
sc_queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);

View File

@@ -17,7 +17,7 @@ struct record_packet {
struct record_packet *next; struct record_packet *next;
}; };
struct recorder_queue SC_QUEUE(struct record_packet); struct recorder_queue QUEUE(struct record_packet);
struct recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait struct sc_packet_sink packet_sink; // packet sink trait

View File

@@ -343,29 +343,19 @@ scrcpy(const struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink); stream_add_sink(&s->stream, &rec->packet_sink);
} }
if (options->control) {
if (!controller_init(&s->controller, s->server.control_socket)) {
goto end;
}
controller_initialized = true;
if (!controller_start(&s->controller)) {
goto end;
}
controller_started = true;
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
if (options->display) { if (options->display) {
if (options->control) {
if (!controller_init(&s->controller, s->server.control_socket)) {
goto end;
}
controller_initialized = true;
if (!controller_start(&s->controller)) {
goto end;
}
controller_started = true;
}
const char *window_title = const char *window_title =
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : device_name;
@@ -389,6 +379,16 @@ scrcpy(const struct scrcpy_options *options) {
screen_initialized = true; screen_initialized = true;
decoder_add_sink(&s->decoder, &s->screen.frame_sink); decoder_add_sink(&s->decoder, &s->screen.frame_sink);
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
} }
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2

View File

@@ -274,16 +274,14 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
static bool static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
static void bool previous_frame_skipped;
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
void *userdata) { if (!ok) {
(void) vb; return false;
struct screen *screen = userdata; }
if (previous_skipped) { if (previous_frame_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter); fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead // this new frame instead
@@ -295,6 +293,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
// Post the event on the UI thread // Post the event on the UI thread
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
return true;
} }
bool bool
@@ -304,25 +304,15 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
static const struct sc_video_buffer_callbacks cbs = { bool ok = video_buffer_init(&screen->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&screen->vb, 1000, &cbs, screen);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer"); LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) { if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter"); LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer; goto error_destroy_video_buffer;
} }
screen->frame_size = params->frame_size; screen->frame_size = params->frame_size;
@@ -463,11 +453,8 @@ error_destroy_window:
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
error_destroy_fps_counter: error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
error_destroy_video_buffer: error_destroy_video_buffer:
sc_video_buffer_destroy(&screen->vb); video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -484,13 +471,11 @@ screen_hide_window(struct screen *screen) {
void void
screen_interrupt(struct screen *screen) { screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter); fps_counter_interrupt(&screen->fps_counter);
} }
void void
screen_join(struct screen *screen) { screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter); fps_counter_join(&screen->fps_counter);
} }
@@ -504,7 +489,7 @@ screen_destroy(struct screen *screen) {
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb); video_buffer_destroy(&screen->vb);
} }
static void static void
@@ -610,7 +595,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
static bool static bool
screen_update_frame(struct screen *screen) { screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame); av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame); video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame; AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(&screen->fps_counter); fps_counter_add_rendered_frame(&screen->fps_counter);

View File

@@ -8,7 +8,6 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "coords.h" #include "coords.h"
#include "fps_counter.h"
#include "opengl.h" #include "opengl.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "video_buffer.h" #include "video_buffer.h"
@@ -20,7 +19,7 @@ struct screen {
bool open; // track the open/close state to assert correct behavior bool open; // track the open/close state to assert correct behavior
#endif #endif
struct sc_video_buffer vb; struct video_buffer vb;
struct fps_counter fps_counter; struct fps_counter fps_counter;
SDL_Window *window; SDL_Window *window;

View File

@@ -557,7 +557,7 @@ server_stop(struct server *server) {
#define WATCHDOG_DELAY_MS 1000 #define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond, signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex, &server->mutex,
sc_tick_now() + WATCHDOG_DELAY_MS); WATCHDOG_DELAY_MS);
} }
sc_mutex_unlock(&server->mutex); sc_mutex_unlock(&server->mutex);

View File

@@ -151,6 +151,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (stream->pending) { if (stream->pending) {
// the pending packet must be discarded (consumed or error) // the pending packet must be discarded (consumed or error)
av_packet_unref(stream->pending);
av_packet_free(&stream->pending); av_packet_free(&stream->pending);
} }
@@ -243,6 +244,7 @@ run_stream(void *data) {
LOGD("End of frames"); LOGD("End of frames");
if (stream->pending) { if (stream->pending) {
av_packet_unref(stream->pending);
av_packet_free(&stream->pending); av_packet_free(&stream->pending);
} }

View File

@@ -1,6 +1,6 @@
// generic intrusive FIFO queue // generic intrusive FIFO queue
#ifndef SC_QUEUE_H #ifndef QUEUE_H
#define SC_QUEUE_H #define QUEUE_H
#include "common.h" #include "common.h"
@@ -10,15 +10,15 @@
// To define a queue type of "struct foo": // To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo); // struct queue_foo QUEUE(struct foo);
#define SC_QUEUE(TYPE) { \ #define QUEUE(TYPE) { \
TYPE *first; \ TYPE *first; \
TYPE *last; \ TYPE *last; \
} }
#define sc_queue_init(PQ) \ #define queue_init(PQ) \
(void) ((PQ)->first = (PQ)->last = NULL) (void) ((PQ)->first = (PQ)->last = NULL)
#define sc_queue_is_empty(PQ) \ #define queue_is_empty(PQ) \
!(PQ)->first !(PQ)->first
// NEXTFIELD is the field in the ITEM type used for intrusive linked-list // NEXTFIELD is the field in the ITEM type used for intrusive linked-list
@@ -30,30 +30,30 @@
// }; // };
// //
// // define the type "struct my_queue" // // define the type "struct my_queue"
// struct my_queue SC_QUEUE(struct foo); // struct my_queue QUEUE(struct foo);
// //
// struct my_queue queue; // struct my_queue queue;
// sc_queue_init(&queue); // queue_init(&queue);
// //
// struct foo v1 = { .value = 42 }; // struct foo v1 = { .value = 42 };
// struct foo v2 = { .value = 27 }; // struct foo v2 = { .value = 27 };
// //
// sc_queue_push(&queue, next, v1); // queue_push(&queue, next, v1);
// sc_queue_push(&queue, next, v2); // queue_push(&queue, next, v2);
// //
// struct foo *foo; // struct foo *foo;
// sc_queue_take(&queue, next, &foo); // queue_take(&queue, next, &foo);
// assert(foo->value == 42); // assert(foo->value == 42);
// sc_queue_take(&queue, next, &foo); // queue_take(&queue, next, &foo);
// assert(foo->value == 27); // assert(foo->value == 27);
// assert(sc_queue_is_empty(&queue)); // assert(queue_is_empty(&queue));
// //
// push a new item into the queue // push a new item into the queue
#define sc_queue_push(PQ, NEXTFIELD, ITEM) \ #define queue_push(PQ, NEXTFIELD, ITEM) \
(void) ({ \ (void) ({ \
(ITEM)->NEXTFIELD = NULL; \ (ITEM)->NEXTFIELD = NULL; \
if (sc_queue_is_empty(PQ)) { \ if (queue_is_empty(PQ)) { \
(PQ)->first = (PQ)->last = (ITEM); \ (PQ)->first = (PQ)->last = (ITEM); \
} else { \ } else { \
(PQ)->last->NEXTFIELD = (ITEM); \ (PQ)->last->NEXTFIELD = (ITEM); \
@@ -65,9 +65,9 @@
// the result is stored in *(PITEM) // the result is stored in *(PITEM)
// (without typeof(), we could not store a local variable having the correct // (without typeof(), we could not store a local variable having the correct
// type so that we can "return" it) // type so that we can "return" it)
#define sc_queue_take(PQ, NEXTFIELD, PITEM) \ #define queue_take(PQ, NEXTFIELD, PITEM) \
(void) ({ \ (void) ({ \
assert(!sc_queue_is_empty(PQ)); \ assert(!queue_is_empty(PQ)); \
*(PITEM) = (PQ)->first; \ *(PITEM) = (PQ)->first; \
(PQ)->first = (PQ)->first->NEXTFIELD; \ (PQ)->first = (PQ)->first->NEXTFIELD; \
}) })

View File

@@ -2,7 +2,6 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h>
#include "log.h" #include "log.h"
@@ -32,7 +31,7 @@ sc_mutex_init(sc_mutex *mutex) {
mutex->mutex = sdl_mutex; mutex->mutex = sdl_mutex;
#ifndef NDEBUG #ifndef NDEBUG
atomic_init(&mutex->locker, 0); mutex->locker = 0;
#endif #endif
return true; return true;
} }
@@ -53,8 +52,7 @@ sc_mutex_lock(sc_mutex *mutex) {
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#else #else
(void) r; (void) r;
#endif #endif
@@ -64,7 +62,7 @@ void
sc_mutex_unlock(sc_mutex *mutex) { sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG #ifndef NDEBUG
assert(sc_mutex_held(mutex)); assert(sc_mutex_held(mutex));
atomic_store_explicit(&mutex->locker, 0, memory_order_relaxed); mutex->locker = 0;
#endif #endif
int r = SDL_UnlockMutex(mutex->mutex); int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
@@ -85,9 +83,7 @@ sc_thread_get_id(void) {
#ifndef NDEBUG #ifndef NDEBUG
bool bool
sc_mutex_held(struct sc_mutex *mutex) { sc_mutex_held(struct sc_mutex *mutex) {
sc_thread_id locker_id = return mutex->locker == sc_thread_get_id();
atomic_load_explicit(&mutex->locker, memory_order_relaxed);
return locker_id == sc_thread_get_id();
} }
#endif #endif
@@ -116,30 +112,22 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#else #else
(void) r; (void) r;
#endif #endif
} }
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
sc_tick now = sc_tick_now(); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
if (deadline <= now) {
return false; // timeout
}
sc_tick delay = deadline - now;
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, delay);
#ifndef NDEBUG #ifndef NDEBUG
if (r < 0) { if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#endif #endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0; return r == 0;
@@ -170,11 +158,3 @@ sc_cond_broadcast(sc_cond *cond) {
(void) r; (void) r;
#endif #endif
} }
sc_tick
sc_tick_now(void) {
// SDL ticks is an unsigned 32 bits, but this is an implementation detail.
// It wraps if the program runs for more than ~49 days, but in practice we
// can assume it does not.
return (sc_tick) SDL_GetTicks();
}

View File

@@ -3,7 +3,6 @@
#include "common.h" #include "common.h"
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@@ -13,10 +12,7 @@ typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond; typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *); typedef int sc_thread_fn(void *);
typedef unsigned sc_thread_id; typedef unsigned int sc_thread_id;
typedef atomic_uint sc_atomic_thread_id;
typedef int64_t sc_tick;
typedef struct sc_thread { typedef struct sc_thread {
SDL_Thread *thread; SDL_Thread *thread;
@@ -25,7 +21,7 @@ typedef struct sc_thread {
typedef struct sc_mutex { typedef struct sc_mutex {
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef NDEBUG #ifndef NDEBUG
sc_atomic_thread_id locker; sc_thread_id locker;
#endif #endif
} sc_mutex; } sc_mutex;
@@ -74,7 +70,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout // return true on signaled, false on timeout
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline); sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void void
sc_cond_signal(sc_cond *cond); sc_cond_signal(sc_cond *cond);
@@ -82,7 +78,4 @@ sc_cond_signal(sc_cond *cond);
void void
sc_cond_broadcast(sc_cond *cond); sc_cond_broadcast(sc_cond *cond);
sc_tick
sc_tick_now(void);
#endif #endif

View File

@@ -112,7 +112,7 @@ run_v4l2_sink(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&vs->mutex); sc_mutex_lock(&vs->mutex);
while (!vs->stopped && !vs->has_frame) { while (!vs->stopped && vs->vb.pending_frame_consumed) {
sc_cond_wait(&vs->cond, &vs->mutex); sc_cond_wait(&vs->cond, &vs->mutex);
} }
@@ -121,11 +121,9 @@ run_v4l2_sink(void *data) {
break; break;
} }
vs->has_frame = false;
sc_mutex_unlock(&vs->mutex); sc_mutex_unlock(&vs->mutex);
sc_video_buffer_consume(&vs->vb, vs->frame); video_buffer_consume(&vs->vb, vs->frame);
bool ok = encode_and_write_frame(vs, vs->frame); bool ok = encode_and_write_frame(vs, vs->frame);
av_frame_unref(vs->frame); av_frame_unref(vs->frame);
if (!ok) { if (!ok) {
@@ -139,42 +137,17 @@ run_v4l2_sink(void *data) {
return 0; return 0;
} }
static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct sc_v4l2_sink *vs = userdata;
if (!previous_skipped) {
sc_mutex_lock(&vs->mutex);
vs->has_frame = true;
sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex);
}
}
static bool static bool
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
static const struct sc_video_buffer_callbacks cbs = { bool ok = video_buffer_init(&vs->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&vs->vb, 1, &cbs, vs);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&vs->vb);
if (!ok) {
LOGE("Could not start video buffer");
goto error_video_buffer_destroy;
}
ok = sc_mutex_init(&vs->mutex); ok = sc_mutex_init(&vs->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex"); LOGC("Could not create mutex");
goto error_video_buffer_stop_and_join; goto error_video_buffer_destroy;
} }
ok = sc_cond_init(&vs->cond); ok = sc_cond_init(&vs->cond);
@@ -268,10 +241,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_av_frame_free; goto error_av_frame_free;
} }
vs->has_frame = false;
vs->header_written = false;
vs->stopped = false;
LOGD("Starting v4l2 thread"); LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
if (!ok) { if (!ok) {
@@ -279,6 +248,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_av_packet_free; goto error_av_packet_free;
} }
vs->header_written = false;
vs->stopped = false;
LOGI("v4l2 sink started to device: %s", vs->device_name); LOGI("v4l2 sink started to device: %s", vs->device_name);
return true; return true;
@@ -299,11 +271,8 @@ error_cond_destroy:
sc_cond_destroy(&vs->cond); sc_cond_destroy(&vs->cond);
error_mutex_destroy: error_mutex_destroy:
sc_mutex_destroy(&vs->mutex); sc_mutex_destroy(&vs->mutex);
error_video_buffer_stop_and_join:
sc_video_buffer_stop(&vs->vb);
sc_video_buffer_join(&vs->vb);
error_video_buffer_destroy: error_video_buffer_destroy:
sc_video_buffer_destroy(&vs->vb); video_buffer_destroy(&vs->vb);
return false; return false;
} }
@@ -315,10 +284,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
sc_cond_signal(&vs->cond); sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex); sc_mutex_unlock(&vs->mutex);
sc_video_buffer_stop(&vs->vb);
sc_thread_join(&vs->thread, NULL); sc_thread_join(&vs->thread, NULL);
sc_video_buffer_join(&vs->vb);
av_packet_free(&vs->packet); av_packet_free(&vs->packet);
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
@@ -328,12 +294,20 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
avformat_free_context(vs->format_ctx); avformat_free_context(vs->format_ctx);
sc_cond_destroy(&vs->cond); sc_cond_destroy(&vs->cond);
sc_mutex_destroy(&vs->mutex); sc_mutex_destroy(&vs->mutex);
sc_video_buffer_destroy(&vs->vb); video_buffer_destroy(&vs->vb);
} }
static bool static bool
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
return sc_video_buffer_push(&vs->vb, frame); bool ok = video_buffer_push(&vs->vb, frame, NULL);
if (!ok) {
return false;
}
// signal possible change of vs->vb.pending_frame_consumed
sc_cond_signal(&vs->cond);
return true;
} }
static bool static bool

View File

@@ -12,7 +12,7 @@
struct sc_v4l2_sink { struct sc_v4l2_sink {
struct sc_frame_sink frame_sink; // frame sink trait struct sc_frame_sink frame_sink; // frame sink trait
struct sc_video_buffer vb; struct video_buffer vb;
AVFormatContext *format_ctx; AVFormatContext *format_ctx;
AVCodecContext *encoder_ctx; AVCodecContext *encoder_ctx;
@@ -22,7 +22,6 @@ struct sc_v4l2_sink {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond cond; sc_cond cond;
bool has_frame;
bool stopped; bool stopped;
bool header_written; bool header_written;

View File

@@ -1,345 +1,88 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "util/log.h" #include "util/log.h"
static void bool
sc_clock_history_init(struct sc_clock_history *history) { video_buffer_init(struct video_buffer *vb) {
history->count = 0; vb->pending_frame = av_frame_alloc();
history->head = 0; if (!vb->pending_frame) {
history->system_left_sum = 0; return false;
history->stream_left_sum = 0;
history->system_right_sum = 0;
history->stream_right_sum = 0;
memset(history->points, 0, sizeof(history->points));
}
static void
sc_clock_history_push(struct sc_clock_history *history,
sc_tick system, sc_tick stream) {
struct sc_clock_point *point = &history->points[history->head];
struct sc_clock_point *mid_point = &history->points[(history->head + SC_CLOCK_RANGE/2) % SC_CLOCK_RANGE];
history->system_left_sum += mid_point->system - point->system;
history->stream_left_sum += mid_point->stream - point->stream;
history->system_right_sum -= mid_point->system;
history->stream_right_sum -= mid_point->stream;
if (history->count == SC_CLOCK_RANGE) {
} else {
++history->count;
} }
point->system = system; vb->tmp_frame = av_frame_alloc();
point->stream = stream; if (!vb->tmp_frame) {
av_frame_free(&vb->pending_frame);
history->system_right_sum += system; return false;
history->stream_right_sum += stream;
history->head = (history->head + 1) % SC_CLOCK_RANGE;
}
static void
sc_clock_history_get_eq(struct sc_clock_history *history, double *coeff,
sc_tick *offset) {
struct sc_clock_point p1 = {
.system = history->system_left_sum * 2 / SC_CLOCK_RANGE,
.stream = history->stream_left_sum * 2 / SC_CLOCK_RANGE,
};
struct sc_clock_point p2 = {
.system = history->system_right_sum * 2 / SC_CLOCK_RANGE,
.stream = history->stream_right_sum * 2 / SC_CLOCK_RANGE,
};
double a = (double) (p2.system - p1.system) / (p2.stream - p1.stream);
fprintf(stderr, "%ld %ld\n", history->system_left_sum, history->system_right_sum);
sc_tick b = (history->system_left_sum + history->system_right_sum) / SC_CLOCK_RANGE
- (sc_tick) ((history->stream_left_sum + history->stream_right_sum) / SC_CLOCK_RANGE * a);
*coeff = a;
*offset = b;
}
//static void
//sc_clock_history_get_average_point(struct sc_clock_history *history,
// struct sc_clock_point *point) {
// assert(history->count);
// point->system = history->system_sum / history->count;
// point->stream = history->stream_sum / history->count;
//}
static void
sc_clock_init(struct sc_clock *clock) {
clock->coeff = 1;
clock->offset = 0;
clock->weight = 0;
clock->last.system = 0;
clock->last.stream = 0;
sc_clock_history_init(&clock->history);
}
static sc_tick
sc_clock_to_system_ts(struct sc_clock *clock, sc_tick stream_ts) {
assert(clock->weight); // sc_clock_update() must have been called
return (sc_tick) (stream_ts * clock->coeff) + clock->offset;
}
static void
sc_clock_update(struct sc_clock *clock, sc_tick now, sc_tick stream_ts) {
double instant_coeff;
if (clock->weight) {
sc_tick system_delta = now - clock->last.system;
sc_tick stream_delta = stream_ts - clock->last.stream;
instant_coeff = (double) system_delta / stream_delta;
} else {
// This is the first update, we cannot compute delta
instant_coeff = 1;
} }
sc_clock_history_push(&clock->history, now, stream_ts); bool ok = sc_mutex_init(&vb->mutex);
if (clock->weight < SC_CLOCK_RANGE) {
++clock->weight;
}
sc_clock_history_get_eq(&clock->history, &clock->coeff, &clock->offset);
// // (1-t) * avg + t * new
// clock->coeff = ((clock->weight - 1) * clock->coeff + instant_coeff)
// / clock->weight;
//
// struct sc_clock_point center;
// sc_clock_history_get_average_point(&clock->history, &center);
//
// clock->offset = center.system - (sc_tick) (center.stream * clock->coeff);
//
LOGD("%g x + %ld", clock->coeff, clock->offset);
clock->last.system = now;
clock->last.stream = stream_ts;
}
static struct sc_video_buffer_frame *
sc_video_buffer_frame_new(const AVFrame *frame) {
struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame));
if (!vb_frame) {
return NULL;
}
vb_frame->frame = av_frame_alloc();
if (!vb_frame->frame) {
free(vb_frame);
return NULL;
}
if (av_frame_ref(vb_frame->frame, frame)) {
av_frame_free(&vb_frame->frame);
free(vb_frame);
return NULL;
}
return vb_frame;
}
static void
sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) {
av_frame_unref(vb_frame->frame);
av_frame_free(&vb_frame->frame);
free(vb_frame);
}
static bool
sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) {
bool previous_skipped;
bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped);
if (!ok) { if (!ok) {
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->tmp_frame);
return false; return false;
} }
vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); // there is initially no frame, so consider it has already been consumed
vb->pending_frame_consumed = true;
return true; return true;
} }
static int void
run_buffering(void *data) { video_buffer_destroy(struct video_buffer *vb) {
struct sc_video_buffer *vb = data; sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->tmp_frame);
}
assert(vb->buffering_ms); static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
for (;;) { AVFrame *tmp = *lhs;
sc_mutex_lock(&vb->b.mutex); *lhs = *rhs;
*rhs = tmp;
while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) {
sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex);
}
if (vb->b.stopped) {
sc_mutex_unlock(&vb->b.mutex);
goto stopped;
}
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_tick now = sc_tick_now();
int64_t pts = vb_frame->frame->pts;
LOGD("==== pts = %ld", pts);
bool timed_out = false;
while (!vb->b.stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_ts(&vb->b.clock, pts)
+ vb->buffering_ms;
if (deadline > now + vb->buffering_ms) {
deadline = now + vb->buffering_ms;
}
timed_out =
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
}
if (vb->b.stopped) {
sc_video_buffer_frame_delete(vb_frame);
sc_mutex_unlock(&vb->b.mutex);
goto stopped;
}
sc_mutex_unlock(&vb->b.mutex);
sc_video_buffer_offer(vb, vb_frame->frame);
sc_video_buffer_frame_delete(vb_frame);
}
stopped:
// Flush queue
while (!sc_queue_is_empty(&vb->b.queue)) {
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_video_buffer_frame_delete(vb_frame);
}
LOGD("Buffering thread ended");
return 0;
} }
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms, video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
const struct sc_video_buffer_callbacks *cbs, bool *previous_frame_skipped) {
void *cbs_userdata) { sc_mutex_lock(&vb->mutex);
bool ok = sc_frame_buffer_init(&vb->fb);
if (!ok) { // Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(vb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false; return false;
} }
if (buffering_ms) { // Now that av_frame_ref() succeeded, we can replace the previous
ok = sc_mutex_init(&vb->b.mutex); // pending_frame
if (!ok) { swap_frames(&vb->pending_frame, &vb->tmp_frame);
LOGC("Could not create mutex"); av_frame_unref(vb->tmp_frame);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.queue_cond); if (previous_frame_skipped) {
if (!ok) { *previous_frame_skipped = !vb->pending_frame_consumed;
LOGC("Could not create cond");
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.wait_cond);
if (!ok) {
LOGC("Could not create wait cond");
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
sc_clock_init(&vb->b.clock);
sc_queue_init(&vb->b.queue);
} }
vb->pending_frame_consumed = false;
assert(cbs); sc_mutex_unlock(&vb->mutex);
assert(cbs->on_new_frame);
vb->buffering_ms = buffering_ms;
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
return true;
}
bool
sc_video_buffer_start(struct sc_video_buffer *vb) {
if (vb->buffering_ms) {
bool ok =
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
if (!ok) {
LOGE("Could not start buffering thread");
return false;
}
}
return true; return true;
} }
void void
sc_video_buffer_stop(struct sc_video_buffer *vb) { video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
if (vb->buffering_ms) { sc_mutex_lock(&vb->mutex);
sc_mutex_lock(&vb->b.mutex); assert(!vb->pending_frame_consumed);
vb->b.stopped = true; vb->pending_frame_consumed = true;
sc_cond_signal(&vb->b.queue_cond);
sc_cond_signal(&vb->b.wait_cond); av_frame_move_ref(dst, vb->pending_frame);
sc_mutex_unlock(&vb->b.mutex); // av_frame_move_ref() resets its source frame, so no need to call
} // av_frame_unref()
}
sc_mutex_unlock(&vb->mutex);
void
sc_video_buffer_join(struct sc_video_buffer *vb) {
if (vb->buffering_ms) {
sc_thread_join(&vb->b.thread, NULL);
}
}
void
sc_video_buffer_destroy(struct sc_video_buffer *vb) {
sc_frame_buffer_destroy(&vb->fb);
if (vb->buffering_ms) {
sc_cond_destroy(&vb->b.wait_cond);
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
}
}
bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) {
if (!vb->buffering_ms) {
// no buffering
return sc_video_buffer_offer(vb, frame);
}
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
if (!vb_frame) {
LOGE("Could not allocate frame");
return false;
}
sc_clock_update(&vb->b.clock, sc_tick_now(), vb_frame->frame->pts);
sc_mutex_lock(&vb->b.mutex);
sc_queue_push(&vb->b.queue, next, vb_frame);
sc_cond_signal(&vb->b.queue_cond);
sc_cond_signal(&vb->b.wait_cond);
sc_mutex_unlock(&vb->b.mutex);
return true;
}
void
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) {
sc_frame_buffer_consume(&vb->fb, dst);
} }

View File

@@ -1,104 +1,50 @@
#ifndef SC_VIDEO_BUFFER_H #ifndef VIDEO_BUFFER_H
#define SC_VIDEO_BUFFER_H #define VIDEO_BUFFER_H
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "frame_buffer.h" #include "fps_counter.h"
#include "util/queue.h"
#include "util/thread.h" #include "util/thread.h"
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
struct sc_video_buffer_frame { /**
AVFrame *frame; * A video buffer holds 1 pending frame, which is the last frame received from
sc_tick system_pts; * the producer (typically, the decoder).
struct sc_video_buffer_frame *next; *
}; * If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*
* The producer and the consumer typically do not live in the same thread.
* That's the reason why the callback on_frame_available() does not provide the
* frame as parameter: the consumer might post an event to its own thread to
* retrieve the pending frame from there, and that frame may have changed since
* the callback if producer pushed a new one in between.
*/
struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame); struct video_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
#define SC_CLOCK_RANGE 32 sc_mutex mutex;
struct sc_clock_point { bool pending_frame_consumed;
sc_tick system;
sc_tick stream;
};
struct sc_clock_history {
struct sc_clock_point points[SC_CLOCK_RANGE];
unsigned count;
unsigned head;
sc_tick system_left_sum;
sc_tick stream_left_sum;
sc_tick system_right_sum;
sc_tick stream_right_sum;
};
struct sc_clock {
double coeff;
sc_tick offset;
unsigned weight; // 0 <= weight && weight <= SC_CLOCK_RANGE
struct {
sc_tick system;
sc_tick stream;
} last;
struct sc_clock_history history;
};
struct sc_video_buffer {
struct sc_frame_buffer fb;
unsigned buffering_ms;
// only if buffering_ms > 0
struct {
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
sc_cond wait_cond;
struct sc_clock clock;
struct sc_video_buffer_frame_queue queue;
bool stopped;
} b; // buffering
const struct sc_video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct sc_video_buffer_callbacks {
void (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata);
}; };
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms, video_buffer_init(struct video_buffer *vb);
const struct sc_video_buffer_callbacks *cbs,
void *cbs_userdata); void
video_buffer_destroy(struct video_buffer *vb);
bool bool
sc_video_buffer_start(struct sc_video_buffer *vb); video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
void void
sc_video_buffer_stop(struct sc_video_buffer *vb); video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
void
sc_video_buffer_join(struct sc_video_buffer *vb);
void
sc_video_buffer_destroy(struct sc_video_buffer *vb);
bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame);
void
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst);
#endif #endif

View File

@@ -10,28 +10,28 @@ struct foo {
}; };
static void test_queue(void) { static void test_queue(void) {
struct my_queue SC_QUEUE(struct foo) queue; struct my_queue QUEUE(struct foo) queue;
sc_queue_init(&queue); queue_init(&queue);
assert(sc_queue_is_empty(&queue)); assert(queue_is_empty(&queue));
struct foo v1 = { .value = 42 }; struct foo v1 = { .value = 42 };
struct foo v2 = { .value = 27 }; struct foo v2 = { .value = 27 };
sc_queue_push(&queue, next, &v1); queue_push(&queue, next, &v1);
sc_queue_push(&queue, next, &v2); queue_push(&queue, next, &v2);
struct foo *foo; struct foo *foo;
assert(!sc_queue_is_empty(&queue)); assert(!queue_is_empty(&queue));
sc_queue_take(&queue, next, &foo); queue_take(&queue, next, &foo);
assert(foo->value == 42); assert(foo->value == 42);
assert(!sc_queue_is_empty(&queue)); assert(!queue_is_empty(&queue));
sc_queue_take(&queue, next, &foo); queue_take(&queue, next, &foo);
assert(foo->value == 27); assert(foo->value == 27);
assert(sc_queue_is_empty(&queue)); assert(queue_is_empty(&queue));
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {