「PCのブラウザでは完璧に再生できる動画が、スマホで見ると動かない…」
動画配信機能を実装していると、必ずと言っていいほど直面する問題です。
筆者は個人開発の動画共有アプリで、GoのバックエンドからFFmpegを使って動画を変換し、S3にアップロードする機能を実装しました。開発中はMacのChromeで問題なく再生できていたのですが、いざ友人にiPhoneでテストしてもらうと「再生ボタンを押しても何も起きない」という報告が。Androidでも「ずっと読み込み中のまま」という状態でした。
原因を調査した結果、コーデックでもネットワークでもなく、動画のビットレートとH.264の Profile/Level設定が問題だったことが判明しました。
本記事では、この問題の原因特定の方法から、モバイル向けの最適化設定、Go言語での実装例まで、実体験をもとに徹底解説します。
1. 発生した症状#
開発環境のPC(macOS Chrome / Safari)では何の問題もなく再生できていました。しかし、実機テストで以下の症状が発生しました。
| デバイス | 症状 |
|---|---|
| iPhone (Safari/Chrome) | 再生ボタンを押すと一瞬再生マークが出るが、すぐに停止する |
| Android (Chrome) | 読み込み中(スピナー)のまま進まない。まれにデコードエラーが表示される |
| 共通 | ネットワークエラー(404等)は出ていない。動画のバイナリ自体はブラウザに届いている |
2. 最初にやるべきこと:ffprobeで動画の状態を確認する#
闇雲に設定を変える前に、まず動画の現在の状態を正しく把握することが重要です。ffprobe を使えば、動画のメタデータを詳細に確認できます。
ビットレートの確認#
ffprobe -v error -select_streams v:0 \
-show_entries stream=bit_rate \
-of default=noprint_wrappers=1:nokey=1 output.mp4Profile / Levelの確認#
ffprobe -v error -select_streams v:0 \
-show_entries stream=profile,level \
-of default=noprint_wrappers=1:nokey=1 output.mp4筆者のケースでの出力結果#
# ビットレート
10485760 ← 約10Mbps(高すぎる!)
# Profile / Level
High ← Highプロファイル
51 ← Level 5.1(高すぎる!)ビットレートが 10Mbps以上 、Profileが High / Level 5.1 ——これがPCでは問題なく、モバイルでは致命的だった原因です。
3. 本当の原因:モバイル端末のデコード能力の限界#
なぜPCでは再生できたのか?#
最近のPCはCPU・GPUともに高性能です。ビットレートが10Mbpsを超えていても、ソフトウェアデコードで力技で再生できてしまいます。
なぜモバイルで失敗したのか?#
モバイル端末(特にiPhone)は、バッテリー持ちと発熱を抑えるためにハードウェアデコーダーに頼って再生します。このチップには厳格な仕様制限があり、以下の条件を超えると「再生不可」と判定されます。
| 原因 | 説明 |
|---|---|
| ビットレートの過多 | 帯域が足りていても、チップの処理能力を超えるとデコードできない |
| H.264 Profile/Level の不一致 | High / Level 5.1 は高画質向け設定。古い端末や安価な端末ではサポートされていない |
| ピクセルフォーマットの不一致 | yuv420p 以外のフォーマット(例: yuv444p)はiOSで再生できないことが多い |
4. 解決策:モバイル向け最適化設定#
改善後のFFmpegコマンド#
ffmpeg -i input.mp4 \
-c:v libx264 \
-profile:v baseline \
-level 3.0 \
-b:v 1500k \
-maxrate 1500k \
-bufsize 3000k \
-pix_fmt yuv420p \
-movflags +faststart \
-c:a aac \
-b:a 128k \
output.mp4各パラメータの解説#
| パラメータ | 役割 | なぜ必要か |
|---|---|---|
-profile:v baseline | 最も互換性の高いH.264プロファイル | 古いiPhone(SE等)でも確実に再生可能 |
-level 3.0 | デコーダーの性能要件を指定 | モバイルなら3.0〜4.1が安定 |
-b:v 1500k | 平均ビットレートを1.5Mbpsに制限 | モバイルでもスムーズに再生可能な範囲 |
-maxrate / -bufsize | 瞬間最大ビットレートを制限 | VBR(可変ビットレート)のスパイクを防止 |
-pix_fmt yuv420p | ピクセルフォーマットを指定 | iOSの標準フォーマット。省略すると再生不可になることが多い |
-movflags +faststart | moovアトムを先頭に移動 | Webでの即座再生に必須(詳細はこちらの記事で解説) |
5. Go言語での実装例#
GoのバックエンドからFFmpegを呼び出す際の実践的な実装例です。タイムアウト制御(context.Context)とエラーハンドリングを含めています。
package video
import (
"bytes"
"context"
"fmt"
"os/exec"
)
// ConvertForMobile は入力動画をモバイル再生に最適化された形式に変換します
func ConvertForMobile(ctx context.Context, inputPath, outputPath string) error {
args := []string{
"-i", inputPath,
"-c:v", "libx264",
"-profile:v", "baseline",
"-level", "3.0",
"-b:v", "1500k",
"-maxrate", "1500k",
"-bufsize", "3000k",
"-pix_fmt", "yuv420p",
"-movflags", "+faststart",
"-c:a", "aac",
"-b:a", "128k",
"-y", // 出力ファイルの上書きを許可
outputPath,
}
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
// FFmpegはログをstderrに出力するためキャプチャ
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("ffmpeg変換エラー: %w (stderr: %s)", err, stderr.String())
}
return nil
}呼び出し側の例#
// 30秒でタイムアウトするContextを作成
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := video.ConvertForMobile(ctx, "input.mp4", "output.mp4"); err != nil {
log.Fatalf("動画変換に失敗: %v", err)
}6. モバイル向け推奨ビットレート目安#
用途に応じたビットレートの目安表です。
| 解像度 | 推奨ビットレート | Profile / Level | 主な用途 |
|---|---|---|---|
| 480p (SD) | 800 〜 1,000 kbps | Baseline / 3.0 | SNS投稿、低速回線対応 |
| 720p (HD) | 1,500 〜 2,500 kbps | Main / 3.1 | Webサイト背景動画、標準画質 |
| 1080p (FHD) | 3,000 〜 5,000 kbps | High / 4.1 | YouTube投稿、Wi-Fi前提 |
ポイント: 迷ったら720p / 1,500kbps / Baselineを選べば、ほぼすべてのモバイル端末で安定して再生できます。
7. 次のステップ:HLS配信の検討#
ここまでの方法はプログレッシブダウンロード(1つのMP4ファイルを直接配信する方式)の最適化です。
以下のような要件がある場合は、HLS(HTTP Live Streaming) への移行を検討してください。
| 要件 | プログレッシブ | HLS |
|---|---|---|
| 回線速度に応じた画質調整 | ❌ 不可 | ✅ アダプティブビットレート |
| 長時間動画(10分以上) | △ 初回読み込みが重い | ✅ セグメント分割で軽量 |
| DRM(著作権保護) | ❌ 不可 | ✅ 対応 |
| 実装の手軽さ | ✅ ファイル配置のみ | △ セグメント生成が必要 |
ブログやポートフォリオの短い動画であればプログレッシブダウンロードで十分ですが、動画がサービスの中心になる場合はHLSの導入を検討しましょう。
8. まとめ:モバイル再生を安定させるチェックリスト#
「PCで再生できる動画」と「モバイルで快適に再生できる動画」は別物です。サーバーサイドで動画を生成・保存する場合、以下のチェックリストを常に確認しましょう。
- ビットレートが高すぎないか?(目安:720pなら2,500kbps以下)
- H.264 Profile は
BaselineまたはMainか? - ピクセルフォーマット は
yuv420pか? - moov atom(faststart) は先頭に配置されているか?
- 実機テスト(最低限iPhoneとAndroid各1台)を行ったか?
よくある質問(FAQ)#
Q. Baselineプロファイルだと画質が落ちませんか?#
同じビットレートで比較すると、MainやHighプロファイルより圧縮効率はやや劣ります。ただし、1,500kbps以上であれば実用上問題ないレベルの画質を維持できます。
Q. faststartだけで解決しないのはなぜですか?#
faststartはmoovアトムの配置問題(再生開始の遅延)を解決しますが、ビットレートやProfile/Levelの問題は解決しません。両方の対策が必要です。詳しくはFFmpegのfaststartに関する記事をご覧ください。
Q. WebMやAV1フォーマットではこの問題は起きませんか?#
起きにくくなりますが、iOSのSafariはWebM/AV1のサポートが限定的です(iOS 16以降で部分的に対応)。現時点ではMP4 + H.264が最も安全な選択肢です。











